@hienlh/ppm 0.23.11 → 0.23.12
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 +18 -0
- package/CLAUDE.md +13 -2
- 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 +37 -1
- package/bun.lock +34 -0
- package/dist/web/assets/accounts-settings-section-CGe5ikvs.js +1 -0
- package/dist/web/assets/accounts-settings-section-CGe5ikvs.js.br +0 -0
- package/dist/web/assets/accounts-settings-section-CGe5ikvs.js.gz +0 -0
- package/dist/web/assets/{agent-session-sheet-CBMsnl_p.js → agent-session-sheet-vBhfHYrC.js} +2 -2
- package/dist/web/assets/agent-session-sheet-vBhfHYrC.js.br +0 -0
- package/dist/web/assets/agent-session-sheet-vBhfHYrC.js.gz +0 -0
- package/dist/web/assets/{agent-session-window-content-D9C9FA7E.js → agent-session-window-content-BxIxDN5i.js} +1 -1
- package/dist/web/assets/agent-session-window-content-BxIxDN5i.js.br +0 -0
- package/dist/web/assets/agent-session-window-content-BxIxDN5i.js.gz +0 -0
- package/dist/web/assets/{ai-resource-editor-DhUVgDEG.js → ai-resource-editor-Y8clBPRv.js} +1 -1
- package/dist/web/assets/ai-resource-editor-Y8clBPRv.js.br +0 -0
- package/dist/web/assets/ai-resource-editor-Y8clBPRv.js.gz +0 -0
- package/dist/web/assets/{ai-settings-section-DOckWvp4.js → ai-settings-section-BB8vscte.js} +1 -1
- package/dist/web/assets/{ai-settings-section-CETpSpYd.js → ai-settings-section-Ck8uN_Bw.js} +1 -1
- package/dist/web/assets/ai-settings-section-Ck8uN_Bw.js.br +0 -0
- package/dist/web/assets/ai-settings-section-Ck8uN_Bw.js.gz +0 -0
- package/dist/web/assets/android-tab-BIOUVVoW.js +2 -0
- package/dist/web/assets/android-tab-BIOUVVoW.js.br +0 -0
- package/dist/web/assets/android-tab-BIOUVVoW.js.gz +0 -0
- package/dist/web/assets/{api-settings-JIgKfcCx.js → api-settings-D1axn9vm.js} +1 -1
- package/dist/web/assets/{appearance-settings-section-BJUiLUb7.js → appearance-settings-section-Bv5_n9lH.js} +1 -1
- package/dist/web/assets/appearance-settings-section-Bv5_n9lH.js.br +0 -0
- package/dist/web/assets/appearance-settings-section-Bv5_n9lH.js.gz +0 -0
- package/dist/web/assets/{apps-panel-1VElRgV3.js → apps-panel-DXHF0QEv.js} +1 -1
- package/dist/web/assets/apps-panel-DXHF0QEv.js.br +0 -0
- package/dist/web/assets/apps-panel-DXHF0QEv.js.gz +0 -0
- package/dist/web/assets/{audio-preview-DYkyHu5X.js → audio-preview-DpjOKEm4.js} +1 -1
- package/dist/web/assets/audio-preview-DpjOKEm4.js.br +0 -0
- package/dist/web/assets/audio-preview-DpjOKEm4.js.gz +0 -0
- package/dist/web/assets/branch-review-tab-4dgMNadQ.js +1 -0
- package/dist/web/assets/branch-review-tab-4dgMNadQ.js.br +0 -0
- package/dist/web/assets/branch-review-tab-4dgMNadQ.js.gz +0 -0
- package/dist/web/assets/chat-tab-Bbe_77oJ.js +15 -0
- package/dist/web/assets/chat-tab-Bbe_77oJ.js.br +0 -0
- package/dist/web/assets/chat-tab-Bbe_77oJ.js.gz +0 -0
- package/dist/web/assets/chat-tab-D3i5O3dO.js +1 -0
- package/dist/web/assets/chat-tab-D3i5O3dO.js.br +0 -0
- package/dist/web/assets/chat-tab-D3i5O3dO.js.gz +0 -0
- package/dist/web/assets/code-editor-DXsIWoQ1.js +7 -0
- package/dist/web/assets/code-editor-DXsIWoQ1.js.br +0 -0
- package/dist/web/assets/code-editor-DXsIWoQ1.js.gz +0 -0
- package/dist/web/assets/{codex-reset-credit-button-Fr45FUxi.js → codex-reset-credit-button-DRhievFQ.js} +1 -1
- package/dist/web/assets/codex-reset-credit-button-DRhievFQ.js.br +0 -0
- package/dist/web/assets/codex-reset-credit-button-DRhievFQ.js.gz +0 -0
- package/dist/web/assets/{conflict-editor-B-wdjSXI.js → conflict-editor-D_RImo_V.js} +3 -3
- package/dist/web/assets/conflict-editor-D_RImo_V.js.br +0 -0
- package/dist/web/assets/conflict-editor-D_RImo_V.js.gz +0 -0
- package/dist/web/assets/{csv-preview-B6RodObB.js → csv-preview-BuFdfLo3.js} +1 -1
- package/dist/web/assets/csv-preview-BuFdfLo3.js.br +0 -0
- package/dist/web/assets/csv-preview-BuFdfLo3.js.gz +0 -0
- package/dist/web/assets/{database-viewer-CdPBUQPY.js → database-viewer-ChcbYn9E.js} +1 -1
- package/dist/web/assets/database-viewer-ChcbYn9E.js.br +0 -0
- package/dist/web/assets/database-viewer-ChcbYn9E.js.gz +0 -0
- package/dist/web/assets/{design-settings-section-BpCpaBwL.js → design-settings-section-B_xOf51h.js} +1 -1
- package/dist/web/assets/design-settings-section-B_xOf51h.js.br +0 -0
- package/dist/web/assets/design-settings-section-B_xOf51h.js.gz +0 -0
- package/dist/web/assets/{design-skill-suggestion-DkT2E2IV.js → design-skill-suggestion-BbEDg15Z.js} +1 -1
- package/dist/web/assets/design-skill-suggestion-BbEDg15Z.js.br +0 -0
- package/dist/web/assets/design-skill-suggestion-BbEDg15Z.js.gz +0 -0
- package/dist/web/assets/{design-tab-BZb9eemI.js → design-tab-BQSYJ4cS.js} +6 -6
- package/dist/web/assets/design-tab-BQSYJ4cS.js.br +0 -0
- package/dist/web/assets/design-tab-BQSYJ4cS.js.gz +0 -0
- package/dist/web/assets/{designs-sidebar-panel-CF4tp6j-.js → designs-sidebar-panel-AiPaYfSU.js} +1 -1
- package/dist/web/assets/designs-sidebar-panel-AiPaYfSU.js.br +0 -0
- package/dist/web/assets/designs-sidebar-panel-AiPaYfSU.js.gz +0 -0
- package/dist/web/assets/{diff-viewer-BqEqwSw6.js → diff-viewer-BZ6glGK-.js} +1 -1
- package/dist/web/assets/diff-viewer-BZ6glGK-.js.br +0 -0
- package/dist/web/assets/diff-viewer-BZ6glGK-.js.gz +0 -0
- package/dist/web/assets/{edit-diff-preview-DD4HayXY.js → edit-diff-preview-D-FNLOU2.js} +1 -1
- package/dist/web/assets/edit-diff-preview-D-FNLOU2.js.br +0 -0
- package/dist/web/assets/edit-diff-preview-D-FNLOU2.js.gz +0 -0
- package/dist/web/assets/{editor-language-service-CkA5sEdV.js → editor-language-service-4sbsdqBV.js} +1 -1
- package/dist/web/assets/editor-language-service-4sbsdqBV.js.br +0 -0
- package/dist/web/assets/editor-language-service-4sbsdqBV.js.gz +0 -0
- package/dist/web/assets/explorer-body-DbgrG1fe.js +2 -0
- package/dist/web/assets/explorer-body-DbgrG1fe.js.br +0 -0
- package/dist/web/assets/explorer-body-DbgrG1fe.js.gz +0 -0
- package/dist/web/assets/{explorer-window-content-CRJ2eMZ4.js → explorer-window-content-BCjWvHpp.js} +1 -1
- package/dist/web/assets/explorer-window-content-BCjWvHpp.js.br +0 -0
- package/dist/web/assets/explorer-window-content-BCjWvHpp.js.gz +0 -0
- package/dist/web/assets/{extension-manager-section-DwQTdCII.js → extension-manager-section-DiMF4RnT.js} +1 -1
- package/dist/web/assets/extension-manager-section-DiMF4RnT.js.br +0 -0
- package/dist/web/assets/extension-manager-section-DiMF4RnT.js.gz +0 -0
- package/dist/web/assets/{extension-webview-CMq-VTLu.js → extension-webview-Coti6Vfw.js} +1 -1
- package/dist/web/assets/extension-webview-Coti6Vfw.js.br +0 -0
- package/dist/web/assets/extension-webview-Coti6Vfw.js.gz +0 -0
- package/dist/web/assets/file-store-Cm7-pHux.js +1 -0
- package/dist/web/assets/file-store-Cm7-pHux.js.br +0 -0
- package/dist/web/assets/file-store-Cm7-pHux.js.gz +0 -0
- package/dist/web/assets/{files-settings-section-B2lcyh-6.js → files-settings-section-ZTVwOwx0.js} +1 -1
- package/dist/web/assets/files-settings-section-ZTVwOwx0.js.br +0 -0
- package/dist/web/assets/files-settings-section-ZTVwOwx0.js.gz +0 -0
- package/dist/web/assets/{general-settings-section-Doepm2sH.js → general-settings-section-B7Hjx_fb.js} +1 -1
- package/dist/web/assets/general-settings-section-B7Hjx_fb.js.br +0 -0
- package/dist/web/assets/general-settings-section-B7Hjx_fb.js.gz +0 -0
- package/dist/web/assets/{git-log-panel-CJakNMDc.js → git-log-panel-CbcJ_YEH.js} +1 -1
- package/dist/web/assets/git-log-panel-CbcJ_YEH.js.br +0 -0
- package/dist/web/assets/git-log-panel-CbcJ_YEH.js.gz +0 -0
- package/dist/web/assets/{glide-data-grid-C_SeOhSq.js → glide-data-grid-BfByGSf1.js} +3 -3
- package/dist/web/assets/glide-data-grid-BfByGSf1.js.br +0 -0
- package/dist/web/assets/glide-data-grid-BfByGSf1.js.gz +0 -0
- package/dist/web/assets/group-chat-tab-DCo9OP3D.js +1 -0
- package/dist/web/assets/group-chat-tab-DCo9OP3D.js.br +0 -0
- package/dist/web/assets/group-chat-tab-DCo9OP3D.js.gz +0 -0
- package/dist/web/assets/{image-preview-C1Frlx22.js → image-preview-Cjlps7Pt.js} +1 -1
- package/dist/web/assets/index-CyDLBddf.js +52 -0
- package/dist/web/assets/index-CyDLBddf.js.br +0 -0
- package/dist/web/assets/index-CyDLBddf.js.gz +0 -0
- package/dist/web/assets/index-Dh5apC3I.css +1 -0
- package/dist/web/assets/index-Dh5apC3I.css.br +0 -0
- package/dist/web/assets/index-Dh5apC3I.css.gz +0 -0
- package/dist/web/assets/{jira-watcher-section-BmkIGQFV.js → jira-watcher-section-Dz_-sUC2.js} +1 -1
- package/dist/web/assets/keybindings-store-BTq5Z7XP.js +1 -0
- package/dist/web/assets/{keyboard-shortcuts-section-BgZe3QDr.js → keyboard-shortcuts-section-Ba5NYwlG.js} +1 -1
- package/dist/web/assets/keyboard-shortcuts-section-Ba5NYwlG.js.br +0 -0
- package/dist/web/assets/keyboard-shortcuts-section-Ba5NYwlG.js.gz +0 -0
- package/dist/web/assets/{language-servers-section-CCMzgOaE.js → language-servers-section-DJJ7SFXo.js} +2 -2
- package/dist/web/assets/language-servers-section-DJJ7SFXo.js.br +0 -0
- package/dist/web/assets/language-servers-section-DJJ7SFXo.js.gz +0 -0
- package/dist/web/assets/{lsp-client-B10suuaZ.js → lsp-client-Dr3vQ1bb.js} +1 -1
- package/dist/web/assets/lsp-client-Dr3vQ1bb.js.br +0 -0
- package/dist/web/assets/lsp-client-Dr3vQ1bb.js.gz +0 -0
- package/dist/web/assets/{markdown-renderer-AHHFS3jc.js → markdown-renderer-CmnqFfTU.js} +3 -3
- package/dist/web/assets/markdown-renderer-CmnqFfTU.js.br +0 -0
- package/dist/web/assets/markdown-renderer-CmnqFfTU.js.gz +0 -0
- package/dist/web/assets/markdown-renderer-LJdVMbGl.js +1 -0
- package/dist/web/assets/{mobile-explorer-sheet-CqCsLWSr.js → mobile-explorer-sheet-BiIhZtJ2.js} +1 -1
- package/dist/web/assets/mobile-explorer-sheet-BiIhZtJ2.js.br +0 -0
- package/dist/web/assets/mobile-explorer-sheet-BiIhZtJ2.js.gz +0 -0
- package/dist/web/assets/new-design-dialog-DI_hxvFV.js +1 -0
- package/dist/web/assets/new-design-dialog-DI_hxvFV.js.br +0 -0
- package/dist/web/assets/new-design-dialog-DI_hxvFV.js.gz +0 -0
- package/dist/web/assets/notification-store-9lBSplOV.js +1 -0
- package/dist/web/assets/{notifications-settings-section-CXgzH_bK.js → notifications-settings-section-0s-2OKRv.js} +1 -1
- package/dist/web/assets/notifications-settings-section-0s-2OKRv.js.br +0 -0
- package/dist/web/assets/notifications-settings-section-0s-2OKRv.js.gz +0 -0
- package/dist/web/assets/{overview-panel-BYB8mgRE.js → overview-panel-3htxfZS2.js} +1 -1
- package/dist/web/assets/overview-panel-3htxfZS2.js.br +0 -0
- package/dist/web/assets/overview-panel-3htxfZS2.js.gz +0 -0
- package/dist/web/assets/panel-store-B6PoCwBA.js +1 -0
- package/dist/web/assets/panel-store-B6PoCwBA.js.br +0 -0
- package/dist/web/assets/panel-store-B6PoCwBA.js.gz +0 -0
- package/dist/web/assets/{performance-panel-Dpuq3g0W.js → performance-panel-V8gpnnf7.js} +2 -2
- package/dist/web/assets/performance-panel-V8gpnnf7.js.br +0 -0
- package/dist/web/assets/performance-panel-V8gpnnf7.js.gz +0 -0
- package/dist/web/assets/{postgres-viewer-DW0Gkmow.js → postgres-viewer-sWS-ogTC.js} +1 -1
- package/dist/web/assets/postgres-viewer-sWS-ogTC.js.br +0 -0
- package/dist/web/assets/postgres-viewer-sWS-ogTC.js.gz +0 -0
- package/dist/web/assets/{ppmbot-settings-section-DFtlGoW6.js → ppmbot-settings-section-CT8vvlzk.js} +1 -1
- package/dist/web/assets/ppmbot-settings-section-CT8vvlzk.js.br +0 -0
- package/dist/web/assets/ppmbot-settings-section-CT8vvlzk.js.gz +0 -0
- package/dist/web/assets/{pptx-export-CNyG32Xr.js → pptx-export-Ugjv0hso.js} +4 -4
- package/dist/web/assets/pptx-export-Ugjv0hso.js.br +0 -0
- package/dist/web/assets/pptx-export-Ugjv0hso.js.gz +0 -0
- package/dist/web/assets/{problems-panel-V5zqpuXq.js → problems-panel-Gcy79pSS.js} +1 -1
- package/dist/web/assets/problems-panel-Gcy79pSS.js.br +0 -0
- package/dist/web/assets/problems-panel-Gcy79pSS.js.gz +0 -0
- package/dist/web/assets/{process-row-format-CiJJSf_V.js → process-row-format-u6y7lOee.js} +1 -1
- package/dist/web/assets/process-table-BUTIp9Oy.js +1 -0
- package/dist/web/assets/process-table-BUTIp9Oy.js.br +0 -0
- package/dist/web/assets/process-table-BUTIp9Oy.js.gz +0 -0
- package/dist/web/assets/{proxy-settings-section-B7AVlTfY.js → proxy-settings-section-Buv_8y54.js} +1 -1
- package/dist/web/assets/proxy-settings-section-Buv_8y54.js.br +0 -0
- package/dist/web/assets/proxy-settings-section-Buv_8y54.js.gz +0 -0
- package/dist/web/assets/{query-audit-section-gHmRfWKr.js → query-audit-section-N2DQtgJx.js} +1 -1
- package/dist/web/assets/query-audit-section-N2DQtgJx.js.br +0 -0
- package/dist/web/assets/query-audit-section-N2DQtgJx.js.gz +0 -0
- package/dist/web/assets/remote-desktop-clipboard-notice-CRvJqmaz.js +1 -0
- package/dist/web/assets/remote-desktop-clipboard-notice-CRvJqmaz.js.br +0 -0
- package/dist/web/assets/remote-desktop-clipboard-notice-CRvJqmaz.js.gz +0 -0
- package/dist/web/assets/remote-desktop-mobile-sheet-BoYINLbJ.js +2 -0
- package/dist/web/assets/remote-desktop-mobile-sheet-BoYINLbJ.js.br +0 -0
- package/dist/web/assets/remote-desktop-mobile-sheet-BoYINLbJ.js.gz +0 -0
- package/dist/web/assets/remote-desktop-mobile-view-DPdP0yYu.js +1 -0
- package/dist/web/assets/remote-desktop-mobile-view-DPdP0yYu.js.br +0 -0
- package/dist/web/assets/remote-desktop-mobile-view-DPdP0yYu.js.gz +0 -0
- package/dist/web/assets/remote-desktop-readiness-gate-BeQ_Jrp7.js +1 -0
- package/dist/web/assets/remote-desktop-readiness-gate-BeQ_Jrp7.js.br +0 -0
- package/dist/web/assets/remote-desktop-readiness-gate-BeQ_Jrp7.js.gz +0 -0
- package/dist/web/assets/remote-desktop-settings-section-Bs4sHS3l.js +1 -0
- package/dist/web/assets/remote-desktop-settings-section-Bs4sHS3l.js.br +0 -0
- package/dist/web/assets/remote-desktop-settings-section-Bs4sHS3l.js.gz +0 -0
- package/dist/web/assets/remote-desktop-window-content-DDr__JxQ.js +1 -0
- package/dist/web/assets/remote-desktop-window-content-DDr__JxQ.js.br +0 -0
- package/dist/web/assets/remote-desktop-window-content-DDr__JxQ.js.gz +0 -0
- package/dist/web/assets/{run-in-terminal-DKBcEGVu.js → run-in-terminal-DCBNtYHQ.js} +1 -1
- package/dist/web/assets/{schedules-settings-section-sYDAsBMO.js → schedules-settings-section-_cHX2qOa.js} +1 -1
- package/dist/web/assets/schedules-settings-section-_cHX2qOa.js.br +0 -0
- package/dist/web/assets/schedules-settings-section-_cHX2qOa.js.gz +0 -0
- package/dist/web/assets/{send-to-chat-CCdOrKc7.js → send-to-chat-dNqNV7Y4.js} +1 -1
- package/dist/web/assets/send-to-chat-dNqNV7Y4.js.br +0 -0
- package/dist/web/assets/send-to-chat-dNqNV7Y4.js.gz +0 -0
- package/dist/web/assets/{services-panel-CKa2aP1d.js → services-panel-Dm54cnW3.js} +1 -1
- package/dist/web/assets/services-panel-Dm54cnW3.js.br +0 -0
- package/dist/web/assets/services-panel-Dm54cnW3.js.gz +0 -0
- package/dist/web/assets/settings-body-DRRQNaDA.js +1 -0
- package/dist/web/assets/settings-body-DRRQNaDA.js.br +0 -0
- package/dist/web/assets/settings-body-DRRQNaDA.js.gz +0 -0
- package/dist/web/assets/settings-section-content-DRaHfvm8.js +1 -0
- package/dist/web/assets/settings-section-content-U3mIx6hF.js +2 -0
- package/dist/web/assets/settings-section-content-U3mIx6hF.js.br +0 -0
- package/dist/web/assets/settings-section-content-U3mIx6hF.js.gz +0 -0
- package/dist/web/assets/{settings-store-DfuiLq_9.js → settings-store-Cyuo8ceh.js} +2 -2
- package/dist/web/assets/settings-store-Cyuo8ceh.js.br +0 -0
- package/dist/web/assets/settings-store-Cyuo8ceh.js.gz +0 -0
- package/dist/web/assets/settings-tab-CB_BhHTK.js +1 -0
- package/dist/web/assets/settings-window-content-CgZIMNX8.js +1 -0
- package/dist/web/assets/{slash-command-picker-CLevA_Gf.js → slash-command-picker-CQlIqMW0.js} +1 -1
- package/dist/web/assets/slash-command-picker-CQlIqMW0.js.br +0 -0
- package/dist/web/assets/slash-command-picker-CQlIqMW0.js.gz +0 -0
- package/dist/web/assets/{sql-query-editor-Db8b0jEr.js → sql-query-editor--Z4bcqd8.js} +1 -1
- package/dist/web/assets/sql-query-editor--Z4bcqd8.js.br +0 -0
- package/dist/web/assets/sql-query-editor--Z4bcqd8.js.gz +0 -0
- package/dist/web/assets/{sqlite-viewer-VzYLMqkD.js → sqlite-viewer-CSmX3qaj.js} +1 -1
- package/dist/web/assets/sqlite-viewer-CSmX3qaj.js.br +0 -0
- package/dist/web/assets/sqlite-viewer-CSmX3qaj.js.gz +0 -0
- package/dist/web/assets/{system-monitor-body-BR60W_mG.js → system-monitor-body-LannpU6H.js} +2 -2
- package/dist/web/assets/system-monitor-body-LannpU6H.js.br +0 -0
- package/dist/web/assets/system-monitor-body-LannpU6H.js.gz +0 -0
- package/dist/web/assets/system-monitor-tab-BfVucwEF.js +1 -0
- package/dist/web/assets/{system-monitor-window-content-BLw1QBXo.js → system-monitor-window-content-kLNW9Ln3.js} +1 -1
- package/dist/web/assets/{tab-host-window-content-BL61SB5m.js → tab-host-window-content-II_FtOYM.js} +1 -1
- package/dist/web/assets/tab-host-window-content-II_FtOYM.js.br +0 -0
- package/dist/web/assets/tab-host-window-content-II_FtOYM.js.gz +0 -0
- package/dist/web/assets/{tab-store-CbPh8CAX.js → tab-store-DCpc0g0Q.js} +1 -1
- package/dist/web/assets/{terminal-tab-B4y6at4h.js → terminal-tab-CTe2polt.js} +1 -1
- package/dist/web/assets/terminal-tab-CTe2polt.js.br +0 -0
- package/dist/web/assets/terminal-tab-CTe2polt.js.gz +0 -0
- package/dist/web/assets/{tool-cards-K0r_yxI0.js → tool-cards-rxLXbBSm.js} +5 -5
- package/dist/web/assets/tool-cards-rxLXbBSm.js.br +0 -0
- package/dist/web/assets/tool-cards-rxLXbBSm.js.gz +0 -0
- package/dist/web/assets/use-h264-canvas-decoder-BfO9QrlH.js +1 -0
- package/dist/web/assets/use-h264-canvas-decoder-BfO9QrlH.js.br +0 -0
- package/dist/web/assets/use-h264-canvas-decoder-BfO9QrlH.js.gz +0 -0
- package/dist/web/assets/{use-inline-blame-BK23icsE.js → use-inline-blame-CxZ_QWzS.js} +3 -3
- package/dist/web/assets/use-inline-blame-CxZ_QWzS.js.br +0 -0
- package/dist/web/assets/use-inline-blame-CxZ_QWzS.js.gz +0 -0
- package/dist/web/assets/{use-lsp-CRdCPCKW.js → use-lsp-gCNE5A9K.js} +1 -1
- package/dist/web/assets/use-lsp-gCNE5A9K.js.br +0 -0
- package/dist/web/assets/use-lsp-gCNE5A9K.js.gz +0 -0
- package/dist/web/assets/{use-monaco-theme-CpXT2uh3.js → use-monaco-theme-DvNGcCvz.js} +1 -1
- package/dist/web/assets/{use-process-kill-BWYX03KF.js → use-process-kill-Cbiu1mbY.js} +1 -1
- package/dist/web/assets/use-process-kill-Cbiu1mbY.js.br +0 -0
- package/dist/web/assets/use-process-kill-Cbiu1mbY.js.gz +0 -0
- package/dist/web/assets/{use-websocket-CiWQKhHr.js → use-websocket-AC7EG1LK.js} +1 -1
- package/dist/web/assets/video-preview-C2Y61WUE.js +1 -0
- package/dist/web/assets/video-preview-C2Y61WUE.js.br +0 -0
- package/dist/web/assets/video-preview-C2Y61WUE.js.gz +0 -0
- package/dist/web/assets/{voice-settings-section-Ceihggqf.js → voice-settings-section-DCXZeLYI.js} +1 -1
- package/dist/web/assets/voice-settings-section-DCXZeLYI.js.br +0 -0
- package/dist/web/assets/voice-settings-section-DCXZeLYI.js.gz +0 -0
- package/dist/web/assets/{window-pip-registry-mDgwpAwP.js → window-pip-registry-9Y9LQQYS.js} +1 -1
- package/dist/web/index.html +50 -7
- 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/architecture/android-emulator-phase0-report-260921.md +310 -0
- package/docs/architecture/android-emulator-phase2-report-260921.md +163 -0
- package/docs/architecture/android-emulator-phase3-report-260921.md +203 -0
- package/docs/architecture/android-emulator-phase4-report-260921.md +191 -0
- package/docs/architecture/android-emulator-plan-260920.md +376 -0
- package/docs/architecture/plugins-and-tracing.md +48 -7
- package/package.json +4 -2
- package/scripts/gen-android-proto.ts +37 -0
- package/scripts/release.sh +3 -1
- package/src/cli/commands/chat-cmd.ts +2 -2
- package/src/providers/claude-agent-sdk-query-options.ts +28 -0
- package/src/providers/claude-agent-sdk.ts +153 -71
- package/src/providers/claude-warm-spare.ts +176 -0
- package/src/providers/provider.interface.ts +1 -0
- package/src/server/index.ts +53 -0
- package/src/server/middleware/gzip-json.ts +13 -1
- package/src/server/routes/android.ts +616 -0
- package/src/server/routes/chat.ts +31 -0
- package/src/server/routes/files.ts +52 -5
- package/src/server/routes/remote-desktop.ts +95 -2
- package/src/server/routes/trace.ts +66 -0
- package/src/server/ws/android.ts +101 -0
- package/src/server/ws/chat.ts +12 -17
- package/src/server/ws/global.ts +13 -1
- package/src/server/ws/remote-desktop.ts +6 -1
- package/src/services/android/android-apk.ts +387 -0
- package/src/services/android/android-capabilities.ts +154 -0
- package/src/services/android/android-flag.ts +12 -0
- package/src/services/android/android-grpc.ts +124 -0
- package/src/services/android/android-input.ts +129 -0
- package/src/services/android/android-logcat.ts +169 -0
- package/src/services/android/android-nonce.ts +50 -0
- package/src/services/android/android-operations.ts +124 -0
- package/src/services/android/android-screenshot.ts +53 -0
- package/src/services/android/android-session.ts +510 -0
- package/src/services/android/android-video.ts +495 -0
- package/src/services/android/avd-list.ts +131 -0
- package/src/services/android/avd-manager.ts +397 -0
- package/src/services/android/device-registry.ts +223 -0
- package/src/services/android/emulator-discovery.ts +160 -0
- package/src/services/android/emulator-launcher.ts +290 -0
- package/src/services/android/proto/emulator-controller-descriptor.ts +10 -0
- package/src/services/android/proto/emulator_controller.proto +1791 -0
- package/src/services/android/sdk-discovery.ts +178 -0
- package/src/services/android/system-images.ts +124 -0
- package/src/services/chat.service.ts +81 -4
- package/src/services/config.service.ts +1 -1
- package/src/services/file-filter.service.ts +2 -61
- package/src/services/file-glob.ts +67 -0
- package/src/services/file-index/file-index-runner.ts +169 -0
- package/src/services/file-index/file-index-worker.ts +73 -0
- package/src/services/file-index/index-search.ts +152 -0
- package/src/services/file-index/index-walk.ts +109 -0
- package/src/services/file-list-index.service.ts +189 -70
- package/src/services/file-watcher/linux-inotify.ts +238 -0
- package/src/services/file-watcher/watch-tree.ts +37 -6
- package/src/services/file-watcher.service.ts +40 -8
- package/src/services/file.service.ts +17 -6
- package/src/services/group-chat/group-chat.service.ts +1 -1
- package/src/services/jira-debug-session.service.ts +1 -1
- package/src/services/ppmbot/ppmbot-delegation.ts +1 -1
- package/src/services/ppmbot/ppmbot-service.ts +1 -1
- package/src/services/project-content-search.service.ts +45 -11
- package/src/services/proxy-agent-turn.ts +7 -3
- package/src/services/remote-desktop/mediamtx-catalog.ts +89 -0
- package/src/services/remote-desktop/mediamtx-config.ts +91 -0
- package/src/services/remote-desktop/mediamtx-install.service.ts +191 -0
- package/src/services/remote-desktop/mediamtx-paths.ts +51 -0
- package/src/services/remote-desktop/mediamtx-process.ts +152 -0
- package/src/services/remote-desktop/remote-desktop-capture-wayland.ts +312 -0
- package/src/services/remote-desktop/remote-desktop-capture.ts +60 -4
- package/src/services/remote-desktop/remote-desktop-portal.ts +529 -0
- package/src/services/remote-desktop/remote-desktop-requirements.ts +56 -20
- package/src/services/remote-desktop/remote-desktop-session.ts +77 -3
- package/src/services/remote-desktop/remote-desktop-whep-registry.ts +36 -0
- package/src/services/scheduler-runner.ts +2 -3
- package/src/services/session-trace/browser-trace-ingest.ts +131 -0
- package/src/services/session-trace/session-trace-cleanup.ts +65 -0
- package/src/services/session-trace/session-trace-db.ts +89 -0
- package/src/services/session-trace/session-trace-store.ts +132 -0
- package/src/services/session-trace/trace-coalescer.ts +86 -0
- package/src/services/session-trace/trace-recorder.ts +304 -0
- package/src/services/session-trace/trace-writer.ts +83 -0
- package/src/shared/android-avd.ts +53 -0
- package/src/shared/android-protocol.ts +184 -0
- package/src/shared/file-index-limits.ts +17 -0
- package/src/shared/session-trace.ts +61 -0
- package/src/types/chat.ts +17 -0
- package/src/types/config.ts +27 -0
- package/src/types/project.ts +9 -0
- package/src/web/app.tsx +5 -0
- package/src/web/components/android/android-apk-upload.ts +75 -0
- package/src/web/components/android/android-avd-create-dialog.tsx +305 -0
- package/src/web/components/android/android-avd-destroy-dialog.tsx +151 -0
- package/src/web/components/android/android-clipboard-panel.tsx +94 -0
- package/src/web/components/android/android-controls.tsx +128 -0
- package/src/web/components/android/android-coords.ts +99 -0
- package/src/web/components/android/android-device-list.tsx +329 -0
- package/src/web/components/android/android-install-panel.tsx +280 -0
- package/src/web/components/android/android-log-filter.ts +75 -0
- package/src/web/components/android/android-logcat-panel.tsx +214 -0
- package/src/web/components/android/android-screenshot-download.ts +56 -0
- package/src/web/components/android/android-tab.tsx +66 -0
- package/src/web/components/android/android-tools-drawer.tsx +76 -0
- package/src/web/components/android/android-viewer.tsx +200 -0
- package/src/web/components/android/android-ws-url.ts +21 -0
- package/src/web/components/android/open-android.ts +21 -0
- package/src/web/components/android/use-android-available.ts +29 -0
- package/src/web/components/android/use-android-keyboard.ts +111 -0
- package/src/web/components/android/use-android-session.ts +277 -0
- package/src/web/components/android/use-android-touch.ts +123 -0
- package/src/web/components/chat/chat-tab.tsx +28 -2
- package/src/web/components/chat/message-input.tsx +3 -5
- package/src/web/components/editor/compare-picker.tsx +12 -2
- package/src/web/components/explorer/file-tree.tsx +6 -7
- package/src/web/components/explorer/use-tree-row-dnd.ts +0 -2
- package/src/web/components/floating-window/window-content-registry.ts +8 -8
- package/src/web/components/layout/command-palette.tsx +31 -9
- package/src/web/components/layout/mobile-drawer.tsx +8 -1
- package/src/web/components/layout/nav-section-rail.tsx +8 -1
- package/src/web/components/layout/reparenting-tab.tsx +2 -2
- package/src/web/components/layout/tab-content.tsx +5 -0
- package/src/web/components/layout/tab-pool.tsx +31 -20
- package/src/web/components/remote-desktop/use-h264-canvas-decoder.ts +23 -1
- package/src/web/components/remote-desktop/use-remote-desktop-connection.ts +64 -6
- package/src/web/components/remote-desktop/use-webrtc-canvas-video.ts +255 -0
- package/src/web/components/root-error-boundary.tsx +5 -0
- package/src/web/components/settings/remote-desktop-settings-section.tsx +176 -0
- package/src/web/components/settings/settings-categories.ts +3 -0
- package/src/web/components/settings/settings-section-content.tsx +25 -18
- package/src/web/hooks/use-chat-prewarm.ts +53 -0
- package/src/web/hooks/use-chat.ts +18 -4
- package/src/web/hooks/use-file-index-invalidation.ts +7 -5
- package/src/web/hooks/use-global-events.ts +12 -1
- package/src/web/hooks/use-remote-file-search.ts +39 -0
- package/src/web/hooks/use-ui-preload.ts +38 -0
- package/src/web/index.html +43 -0
- package/src/web/lib/chunk-recovery.ts +5 -1
- package/src/web/lib/device-id.ts +39 -0
- package/src/web/lib/feature-badges.ts +2 -0
- package/src/web/lib/lazy-with-preload.tsx +43 -0
- package/src/web/lib/preload-ui.ts +60 -0
- package/src/web/lib/score-file-search.ts +27 -14
- package/src/web/lib/tab-type-icons.ts +2 -1
- package/src/web/lib/trace-boot.ts +8 -0
- package/src/web/lib/trace-client.ts +516 -0
- package/src/web/main.tsx +12 -0
- package/src/web/stores/file-store.ts +124 -18
- package/src/web/stores/panel-utils.ts +3 -0
- package/src/web/stores/tab-store.ts +2 -1
- package/src/web/stores/window-panel-persistence.ts +3 -0
- package/vite.config.ts +3 -0
- package/dist/web/assets/accounts-settings-section-DEIm-W-D.js +0 -1
- package/dist/web/assets/accounts-settings-section-DEIm-W-D.js.br +0 -0
- package/dist/web/assets/accounts-settings-section-DEIm-W-D.js.gz +0 -0
- package/dist/web/assets/agent-session-sheet-CBMsnl_p.js.br +0 -0
- package/dist/web/assets/agent-session-sheet-CBMsnl_p.js.gz +0 -0
- package/dist/web/assets/agent-session-window-content-D9C9FA7E.js.br +0 -0
- package/dist/web/assets/agent-session-window-content-D9C9FA7E.js.gz +0 -0
- package/dist/web/assets/ai-resource-editor-DhUVgDEG.js.br +0 -0
- package/dist/web/assets/ai-resource-editor-DhUVgDEG.js.gz +0 -0
- package/dist/web/assets/ai-settings-section-CETpSpYd.js.br +0 -0
- package/dist/web/assets/ai-settings-section-CETpSpYd.js.gz +0 -0
- package/dist/web/assets/appearance-settings-section-BJUiLUb7.js.br +0 -0
- package/dist/web/assets/appearance-settings-section-BJUiLUb7.js.gz +0 -0
- package/dist/web/assets/apps-panel-1VElRgV3.js.br +0 -0
- package/dist/web/assets/apps-panel-1VElRgV3.js.gz +0 -0
- package/dist/web/assets/audio-preview-DYkyHu5X.js.br +0 -0
- package/dist/web/assets/audio-preview-DYkyHu5X.js.gz +0 -0
- package/dist/web/assets/branch-review-tab-DhTMSpSU.js +0 -1
- package/dist/web/assets/branch-review-tab-DhTMSpSU.js.br +0 -0
- package/dist/web/assets/branch-review-tab-DhTMSpSU.js.gz +0 -0
- package/dist/web/assets/chat-tab-Cg3BKugo.js +0 -15
- package/dist/web/assets/chat-tab-Cg3BKugo.js.br +0 -0
- package/dist/web/assets/chat-tab-Cg3BKugo.js.gz +0 -0
- package/dist/web/assets/chat-tab-tbUDK__Y.js +0 -1
- package/dist/web/assets/chat-tab-tbUDK__Y.js.br +0 -0
- package/dist/web/assets/chat-tab-tbUDK__Y.js.gz +0 -0
- package/dist/web/assets/code-editor-6ZiQehbr.js +0 -7
- package/dist/web/assets/code-editor-6ZiQehbr.js.br +0 -0
- package/dist/web/assets/code-editor-6ZiQehbr.js.gz +0 -0
- package/dist/web/assets/codex-reset-credit-button-Fr45FUxi.js.br +0 -0
- package/dist/web/assets/codex-reset-credit-button-Fr45FUxi.js.gz +0 -0
- package/dist/web/assets/conflict-editor-B-wdjSXI.js.br +0 -0
- package/dist/web/assets/conflict-editor-B-wdjSXI.js.gz +0 -0
- package/dist/web/assets/csv-preview-B6RodObB.js.br +0 -0
- package/dist/web/assets/csv-preview-B6RodObB.js.gz +0 -0
- package/dist/web/assets/database-viewer-CdPBUQPY.js.br +0 -0
- package/dist/web/assets/database-viewer-CdPBUQPY.js.gz +0 -0
- package/dist/web/assets/design-settings-section-BpCpaBwL.js.br +0 -0
- package/dist/web/assets/design-settings-section-BpCpaBwL.js.gz +0 -0
- package/dist/web/assets/design-skill-suggestion-DkT2E2IV.js.br +0 -0
- package/dist/web/assets/design-skill-suggestion-DkT2E2IV.js.gz +0 -0
- package/dist/web/assets/design-tab-BZb9eemI.js.br +0 -0
- package/dist/web/assets/design-tab-BZb9eemI.js.gz +0 -0
- package/dist/web/assets/designs-sidebar-panel-CF4tp6j-.js.br +0 -0
- package/dist/web/assets/designs-sidebar-panel-CF4tp6j-.js.gz +0 -0
- package/dist/web/assets/diff-viewer-BqEqwSw6.js.br +0 -0
- package/dist/web/assets/diff-viewer-BqEqwSw6.js.gz +0 -0
- package/dist/web/assets/edit-diff-preview-DD4HayXY.js.br +0 -0
- package/dist/web/assets/edit-diff-preview-DD4HayXY.js.gz +0 -0
- package/dist/web/assets/editor-language-service-CkA5sEdV.js.br +0 -0
- package/dist/web/assets/editor-language-service-CkA5sEdV.js.gz +0 -0
- package/dist/web/assets/explorer-body-Ds7oKuz7.js +0 -2
- package/dist/web/assets/explorer-body-Ds7oKuz7.js.br +0 -0
- package/dist/web/assets/explorer-body-Ds7oKuz7.js.gz +0 -0
- package/dist/web/assets/explorer-window-content-CRJ2eMZ4.js.br +0 -0
- package/dist/web/assets/explorer-window-content-CRJ2eMZ4.js.gz +0 -0
- package/dist/web/assets/extension-manager-section-DwQTdCII.js.br +0 -0
- package/dist/web/assets/extension-manager-section-DwQTdCII.js.gz +0 -0
- package/dist/web/assets/extension-webview-CMq-VTLu.js.br +0 -0
- package/dist/web/assets/extension-webview-CMq-VTLu.js.gz +0 -0
- package/dist/web/assets/file-store-DY1_0nzY.js +0 -1
- package/dist/web/assets/file-store-DY1_0nzY.js.br +0 -0
- package/dist/web/assets/file-store-DY1_0nzY.js.gz +0 -0
- package/dist/web/assets/files-settings-section-B2lcyh-6.js.br +0 -0
- package/dist/web/assets/files-settings-section-B2lcyh-6.js.gz +0 -0
- package/dist/web/assets/general-settings-section-Doepm2sH.js.br +0 -0
- package/dist/web/assets/general-settings-section-Doepm2sH.js.gz +0 -0
- package/dist/web/assets/git-log-panel-CJakNMDc.js.br +0 -0
- package/dist/web/assets/git-log-panel-CJakNMDc.js.gz +0 -0
- package/dist/web/assets/glide-data-grid-C_SeOhSq.js.br +0 -0
- package/dist/web/assets/glide-data-grid-C_SeOhSq.js.gz +0 -0
- package/dist/web/assets/group-chat-tab--by1xCQW.js +0 -1
- package/dist/web/assets/group-chat-tab--by1xCQW.js.br +0 -0
- package/dist/web/assets/group-chat-tab--by1xCQW.js.gz +0 -0
- package/dist/web/assets/index-DhWjx5iR.js +0 -52
- package/dist/web/assets/index-DhWjx5iR.js.br +0 -0
- package/dist/web/assets/index-DhWjx5iR.js.gz +0 -0
- package/dist/web/assets/index-oIvYSKTT.css +0 -1
- package/dist/web/assets/index-oIvYSKTT.css.br +0 -0
- package/dist/web/assets/index-oIvYSKTT.css.gz +0 -0
- package/dist/web/assets/keybindings-store-30LYZU0l.js +0 -1
- package/dist/web/assets/keyboard-shortcuts-section-BgZe3QDr.js.br +0 -0
- package/dist/web/assets/keyboard-shortcuts-section-BgZe3QDr.js.gz +0 -0
- package/dist/web/assets/language-servers-section-CCMzgOaE.js.br +0 -0
- package/dist/web/assets/language-servers-section-CCMzgOaE.js.gz +0 -0
- package/dist/web/assets/lsp-client-B10suuaZ.js.br +0 -0
- package/dist/web/assets/lsp-client-B10suuaZ.js.gz +0 -0
- package/dist/web/assets/markdown-renderer-AHHFS3jc.js.br +0 -0
- package/dist/web/assets/markdown-renderer-AHHFS3jc.js.gz +0 -0
- package/dist/web/assets/markdown-renderer-DxnXtBoj.js +0 -1
- package/dist/web/assets/mobile-explorer-sheet-CqCsLWSr.js.br +0 -0
- package/dist/web/assets/mobile-explorer-sheet-CqCsLWSr.js.gz +0 -0
- package/dist/web/assets/new-design-dialog-B9tgnQXq.js +0 -1
- package/dist/web/assets/new-design-dialog-B9tgnQXq.js.br +0 -0
- package/dist/web/assets/new-design-dialog-B9tgnQXq.js.gz +0 -0
- package/dist/web/assets/notification-store-HUVzO_bp.js +0 -1
- package/dist/web/assets/notifications-settings-section-CXgzH_bK.js.br +0 -0
- package/dist/web/assets/notifications-settings-section-CXgzH_bK.js.gz +0 -0
- package/dist/web/assets/overview-panel-BYB8mgRE.js.br +0 -0
- package/dist/web/assets/overview-panel-BYB8mgRE.js.gz +0 -0
- package/dist/web/assets/panel-store-CWHPmHwj.js +0 -1
- package/dist/web/assets/panel-store-CWHPmHwj.js.br +0 -0
- package/dist/web/assets/panel-store-CWHPmHwj.js.gz +0 -0
- package/dist/web/assets/performance-panel-Dpuq3g0W.js.br +0 -0
- package/dist/web/assets/performance-panel-Dpuq3g0W.js.gz +0 -0
- package/dist/web/assets/postgres-viewer-DW0Gkmow.js.br +0 -0
- package/dist/web/assets/postgres-viewer-DW0Gkmow.js.gz +0 -0
- package/dist/web/assets/ppmbot-settings-section-DFtlGoW6.js.br +0 -0
- package/dist/web/assets/ppmbot-settings-section-DFtlGoW6.js.gz +0 -0
- package/dist/web/assets/pptx-export-CNyG32Xr.js.br +0 -0
- package/dist/web/assets/pptx-export-CNyG32Xr.js.gz +0 -0
- package/dist/web/assets/problems-panel-V5zqpuXq.js.br +0 -0
- package/dist/web/assets/problems-panel-V5zqpuXq.js.gz +0 -0
- package/dist/web/assets/process-table-DAj46HIl.js +0 -1
- package/dist/web/assets/process-table-DAj46HIl.js.br +0 -0
- package/dist/web/assets/process-table-DAj46HIl.js.gz +0 -0
- package/dist/web/assets/proxy-settings-section-B7AVlTfY.js.br +0 -0
- package/dist/web/assets/proxy-settings-section-B7AVlTfY.js.gz +0 -0
- package/dist/web/assets/query-audit-section-gHmRfWKr.js.br +0 -0
- package/dist/web/assets/query-audit-section-gHmRfWKr.js.gz +0 -0
- package/dist/web/assets/remote-desktop-clipboard-notice-Cgoznh8L.js +0 -1
- package/dist/web/assets/remote-desktop-clipboard-notice-Cgoznh8L.js.br +0 -0
- package/dist/web/assets/remote-desktop-clipboard-notice-Cgoznh8L.js.gz +0 -0
- package/dist/web/assets/remote-desktop-mobile-sheet-X12QbPhO.js +0 -2
- package/dist/web/assets/remote-desktop-mobile-sheet-X12QbPhO.js.br +0 -0
- package/dist/web/assets/remote-desktop-mobile-sheet-X12QbPhO.js.gz +0 -0
- package/dist/web/assets/remote-desktop-mobile-view-DsSHawSK.js +0 -1
- package/dist/web/assets/remote-desktop-mobile-view-DsSHawSK.js.br +0 -0
- package/dist/web/assets/remote-desktop-mobile-view-DsSHawSK.js.gz +0 -0
- package/dist/web/assets/remote-desktop-readiness-gate-CHiQEd9Q.js +0 -1
- package/dist/web/assets/remote-desktop-readiness-gate-CHiQEd9Q.js.br +0 -0
- package/dist/web/assets/remote-desktop-readiness-gate-CHiQEd9Q.js.gz +0 -0
- package/dist/web/assets/remote-desktop-window-content-BnznIby3.js +0 -1
- package/dist/web/assets/remote-desktop-window-content-BnznIby3.js.br +0 -0
- package/dist/web/assets/remote-desktop-window-content-BnznIby3.js.gz +0 -0
- package/dist/web/assets/schedules-settings-section-sYDAsBMO.js.br +0 -0
- package/dist/web/assets/schedules-settings-section-sYDAsBMO.js.gz +0 -0
- package/dist/web/assets/send-to-chat-CCdOrKc7.js.br +0 -0
- package/dist/web/assets/send-to-chat-CCdOrKc7.js.gz +0 -0
- package/dist/web/assets/services-panel-CKa2aP1d.js.br +0 -0
- package/dist/web/assets/services-panel-CKa2aP1d.js.gz +0 -0
- package/dist/web/assets/settings-body-CEIF8lkl.js +0 -2
- package/dist/web/assets/settings-body-CEIF8lkl.js.br +0 -0
- package/dist/web/assets/settings-body-CEIF8lkl.js.gz +0 -0
- package/dist/web/assets/settings-store-DfuiLq_9.js.br +0 -0
- package/dist/web/assets/settings-store-DfuiLq_9.js.gz +0 -0
- package/dist/web/assets/settings-tab-Du5AUPni.js +0 -1
- package/dist/web/assets/settings-window-content-BTflfLez.js +0 -1
- package/dist/web/assets/slash-command-picker-CLevA_Gf.js.br +0 -0
- package/dist/web/assets/slash-command-picker-CLevA_Gf.js.gz +0 -0
- package/dist/web/assets/sql-query-editor-Db8b0jEr.js.br +0 -0
- package/dist/web/assets/sql-query-editor-Db8b0jEr.js.gz +0 -0
- package/dist/web/assets/sqlite-viewer-VzYLMqkD.js.br +0 -0
- package/dist/web/assets/sqlite-viewer-VzYLMqkD.js.gz +0 -0
- package/dist/web/assets/system-monitor-body-BR60W_mG.js.br +0 -0
- package/dist/web/assets/system-monitor-body-BR60W_mG.js.gz +0 -0
- package/dist/web/assets/system-monitor-tab-BYpantE2.js +0 -1
- package/dist/web/assets/tab-host-window-content-BL61SB5m.js.br +0 -0
- package/dist/web/assets/tab-host-window-content-BL61SB5m.js.gz +0 -0
- package/dist/web/assets/terminal-tab-B4y6at4h.js.br +0 -0
- package/dist/web/assets/terminal-tab-B4y6at4h.js.gz +0 -0
- package/dist/web/assets/tool-cards-K0r_yxI0.js.br +0 -0
- package/dist/web/assets/tool-cards-K0r_yxI0.js.gz +0 -0
- package/dist/web/assets/use-inline-blame-BK23icsE.js.br +0 -0
- package/dist/web/assets/use-inline-blame-BK23icsE.js.gz +0 -0
- package/dist/web/assets/use-lsp-CRdCPCKW.js.br +0 -0
- package/dist/web/assets/use-lsp-CRdCPCKW.js.gz +0 -0
- package/dist/web/assets/use-process-kill-BWYX03KF.js.br +0 -0
- package/dist/web/assets/use-process-kill-BWYX03KF.js.gz +0 -0
- package/dist/web/assets/video-preview-DugVkMB7.js +0 -1
- package/dist/web/assets/video-preview-DugVkMB7.js.br +0 -0
- package/dist/web/assets/video-preview-DugVkMB7.js.gz +0 -0
- package/dist/web/assets/voice-settings-section-Ceihggqf.js.br +0 -0
- package/dist/web/assets/voice-settings-section-Ceihggqf.js.gz +0 -0
- /package/dist/web/assets/{__vite-browser-external-B_XMEguz.js → __vite-browser-external-C5mXParC.js} +0 -0
- /package/dist/web/assets/{agent-session-sheet-store-DO682WSg.js → agent-session-sheet-store-_OeezCXB.js} +0 -0
- /package/dist/web/assets/{api-settings-CpO8SPk9.js → api-settings-Cc_CpfmC.js} +0 -0
- /package/dist/web/assets/{api-settings-CpO8SPk9.js.br → api-settings-Cc_CpfmC.js.br} +0 -0
- /package/dist/web/assets/{api-settings-CpO8SPk9.js.gz → api-settings-Cc_CpfmC.js.gz} +0 -0
- /package/dist/web/assets/{api-themes-CJipDH_l.js → api-themes-BtMBSjuM.js} +0 -0
- /package/dist/web/assets/{can-open-in-ppm-Bw_xiOXX.js → can-open-in-ppm-DeE2-kd5.js} +0 -0
- /package/dist/web/assets/{can-open-in-ppm-Bw_xiOXX.js.br → can-open-in-ppm-DeE2-kd5.js.br} +0 -0
- /package/dist/web/assets/{can-open-in-ppm-Bw_xiOXX.js.gz → can-open-in-ppm-DeE2-kd5.js.gz} +0 -0
- /package/dist/web/assets/{csv-parser-DcRWxcNV.js → csv-parser-CoxPdyLw.js} +0 -0
- /package/dist/web/assets/{csv-parser-DcRWxcNV.js.br → csv-parser-CoxPdyLw.js.br} +0 -0
- /package/dist/web/assets/{csv-parser-DcRWxcNV.js.gz → csv-parser-CoxPdyLw.js.gz} +0 -0
- /package/dist/web/assets/{design-export-client-CHffN3DM.js → design-export-client-BfdZB7xK.js} +0 -0
- /package/dist/web/assets/{design-export-client-CHffN3DM.js.br → design-export-client-BfdZB7xK.js.br} +0 -0
- /package/dist/web/assets/{design-export-client-CHffN3DM.js.gz → design-export-client-BfdZB7xK.js.gz} +0 -0
- /package/dist/web/assets/{design-responsive-dialog-DSCpv6O1.js → design-responsive-dialog-BiYy3ibS.js} +0 -0
- /package/dist/web/assets/{design-responsive-dialog-DSCpv6O1.js.br → design-responsive-dialog-BiYy3ibS.js.br} +0 -0
- /package/dist/web/assets/{design-responsive-dialog-DSCpv6O1.js.gz → design-responsive-dialog-BiYy3ibS.js.gz} +0 -0
- /package/dist/web/assets/{design-system-status-label-B_0I_3a8.js → design-system-status-label-ekiLyjKn.js} +0 -0
- /package/dist/web/assets/{docx-preview-B3eJyY2y.js → docx-preview-Dt6RQpfA.js} +0 -0
- /package/dist/web/assets/{docx-preview-B3eJyY2y.js.br → docx-preview-Dt6RQpfA.js.br} +0 -0
- /package/dist/web/assets/{docx-preview-B3eJyY2y.js.gz → docx-preview-Dt6RQpfA.js.gz} +0 -0
- /package/dist/web/assets/{editor-font-BNwo7zo5.js → editor-font-CBszn6vt.js} +0 -0
- /package/dist/web/assets/{format-bytes-CRZfxO2J.js → format-bytes-FQmpZ25A.js} +0 -0
- /package/dist/web/assets/{image-gallery-DC3gRAqk.js → image-gallery-B16hO6Ab.js} +0 -0
- /package/dist/web/assets/{image-preview-geometry-DN3DZQp0.js → image-preview-geometry-D4Ihz4UV.js} +0 -0
- /package/dist/web/assets/{lib-B9k5tn9s.js → lib-6APiJkU3.js} +0 -0
- /package/dist/web/assets/{lib-B9k5tn9s.js.br → lib-6APiJkU3.js.br} +0 -0
- /package/dist/web/assets/{lib-B9k5tn9s.js.gz → lib-6APiJkU3.js.gz} +0 -0
- /package/dist/web/assets/{libesm-5f7T4AHA.js → libesm-5wREk7JN.js} +0 -0
- /package/dist/web/assets/{libesm-5f7T4AHA.js.br → libesm-5wREk7JN.js.br} +0 -0
- /package/dist/web/assets/{libesm-5f7T4AHA.js.gz → libesm-5wREk7JN.js.gz} +0 -0
- /package/dist/web/assets/{media-url-CCGOIxzS.js → media-url-CrT-qulZ.js} +0 -0
- /package/dist/web/assets/{metric-chart-canvas-CjFbZuhk.js → metric-chart-canvas-D7f-LQ8x.js} +0 -0
- /package/dist/web/assets/{metric-chart-canvas-CjFbZuhk.js.br → metric-chart-canvas-D7f-LQ8x.js.br} +0 -0
- /package/dist/web/assets/{metric-chart-canvas-CjFbZuhk.js.gz → metric-chart-canvas-D7f-LQ8x.js.gz} +0 -0
- /package/dist/web/assets/{monaco-dotenv-language-Bwq3gQsi.js → monaco-dotenv-language-DbSss7YR.js} +0 -0
- /package/dist/web/assets/{onboarding-entry-CuH1Kqwu.js → onboarding-entry-CGFdGLLP.js} +0 -0
- /package/dist/web/assets/{onboarding-entry-CuH1Kqwu.js.br → onboarding-entry-CGFdGLLP.js.br} +0 -0
- /package/dist/web/assets/{onboarding-entry-CuH1Kqwu.js.gz → onboarding-entry-CGFdGLLP.js.gz} +0 -0
- /package/dist/web/assets/{pdf-preview-X8bdpfE9.js → pdf-preview-B8A4T-ES.js} +0 -0
- /package/dist/web/assets/{pdf-preview-X8bdpfE9.js.br → pdf-preview-B8A4T-ES.js.br} +0 -0
- /package/dist/web/assets/{pdf-preview-X8bdpfE9.js.gz → pdf-preview-B8A4T-ES.js.gz} +0 -0
- /package/dist/web/assets/{separator-DMu7VFQa.js → separator-dHj3Km--.js} +0 -0
- /package/dist/web/assets/{sortable-header-CipIP7aH.js → sortable-header-BOa8o7NJ.js} +0 -0
- /package/dist/web/assets/{sortable-header-CipIP7aH.js.br → sortable-header-BOa8o7NJ.js.br} +0 -0
- /package/dist/web/assets/{sortable-header-CipIP7aH.js.gz → sortable-header-BOa8o7NJ.js.gz} +0 -0
- /package/dist/web/assets/{use-blob-url-DjuRQWnR.js → use-blob-url-BC69n15x.js} +0 -0
- /package/dist/web/assets/{vendor-xterm-Be4L1NHd.js → vendor-xterm-CfasyE0m.js} +0 -0
- /package/dist/web/assets/{vendor-xterm-Be4L1NHd.js.br → vendor-xterm-CfasyE0m.js.br} +0 -0
- /package/dist/web/assets/{vendor-xterm-Be4L1NHd.js.gz → vendor-xterm-CfasyE0m.js.gz} +0 -0
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wayland screen capture: `gst-launch-1.0` reading the desktop portal's PipeWire node.
|
|
3
|
+
*
|
|
4
|
+
* This is a second capture backend beside the ffmpeg one in `remote-desktop-capture.ts`, not a
|
|
5
|
+
* variant of it, and the split is forced rather than chosen: ffmpeg can only read PipeWire if it
|
|
6
|
+
* was built `--enable-libpipewire`, which distributions overwhelmingly do not do (Arch's own
|
|
7
|
+
* `ffmpeg 2:9.0.1-4.1` does not, so `pipewiregrab` is absent on the host this was written on).
|
|
8
|
+
* GStreamer's `pipewiresrc` ships in a small separate package that a desktop usually already
|
|
9
|
+
* has. It is also exactly what RustDesk does — `Depends On: gst-plugins-base gst-plugin-pipewire`
|
|
10
|
+
* — so this is the well-trodden path, not a workaround.
|
|
11
|
+
*
|
|
12
|
+
* The output is byte-identical to the ffmpeg backend's: H.264 Annex-B access units on stdout.
|
|
13
|
+
* Everything downstream — the access-unit assembler, the WebSocket transport, the MediaMTX
|
|
14
|
+
* relay, the quality ladder — is untouched and cannot tell the two apart.
|
|
15
|
+
*
|
|
16
|
+
* Three measured details are encoded here, each of which fails silently if changed:
|
|
17
|
+
*
|
|
18
|
+
* - **The frame rate is capped by a capsfilter, never by `videorate max-rate=`.** Measured: with
|
|
19
|
+
* `max-rate=30` the pipeline delivered 59 fps, and with `max-rate=60` it delivered 133 — the
|
|
20
|
+
* property does not cap, it only hints. `videorate ! video/x-raw,framerate=30/1` caps for real.
|
|
21
|
+
* - **That same capsfilter is what makes an idle desktop watchable.** A portal stream is
|
|
22
|
+
* damage-driven: on a still screen the compositor pushed **14 frames in 60 seconds**, so a
|
|
23
|
+
* viewer joining a quiet host would see nothing at all until something moved. `videorate`
|
|
24
|
+
* duplicates the last frame to hold cadence — measured 29.6 fps on a completely static screen
|
|
25
|
+
* — which also means keyframes keep arriving, so a late joiner syncs within one GOP.
|
|
26
|
+
* - **`vapostproc` does the colour conversion on the GPU and it is worth the branch.** The
|
|
27
|
+
* portal hands out BGRA; converting that to NV12 in software cost 52% of a core at 1080p,
|
|
28
|
+
* against 14.9% for the whole GPU pipeline at the same resolution and a 30 fps cap.
|
|
29
|
+
*/
|
|
30
|
+
import { CaptureUnavailableError, type CaptureHandle, type StartCaptureOptions } from "./remote-desktop-capture.ts";
|
|
31
|
+
import { AccessUnitAssembler } from "./access-unit-assembler.ts";
|
|
32
|
+
import { DEFAULT_FPS, type QualityPreset } from "../../shared/remote-desktop-quality.ts";
|
|
33
|
+
import { startPortalScreenCast, type PortalScreenCast } from "./remote-desktop-portal.ts";
|
|
34
|
+
import { linuxSessionEnv, type LinuxSession } from "./remote-desktop-linux-session.ts";
|
|
35
|
+
|
|
36
|
+
/** Which GStreamer elements this host actually has. Probed once — `gst-inspect-1.0` is ~40 ms
|
|
37
|
+
* and this sits on the session-start path. */
|
|
38
|
+
export interface GstElements {
|
|
39
|
+
/** `gst-launch-1.0` itself. */
|
|
40
|
+
launch: string | null;
|
|
41
|
+
/** The portal source. Without it there is no Wayland capture at all. */
|
|
42
|
+
pipewiresrc: boolean;
|
|
43
|
+
/** GPU colour conversion; falls back to `videoconvert` on the CPU. */
|
|
44
|
+
vapostproc: boolean;
|
|
45
|
+
/** Hardware H.264; falls back to `x264enc`. */
|
|
46
|
+
vah264enc: boolean;
|
|
47
|
+
x264enc: boolean;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
let cachedElements: GstElements | null = null;
|
|
51
|
+
|
|
52
|
+
async function hasElement(name: string): Promise<boolean> {
|
|
53
|
+
try {
|
|
54
|
+
const probe = Bun.spawn(["gst-inspect-1.0", name], {
|
|
55
|
+
stdout: "ignore", stderr: "ignore", stdin: "ignore",
|
|
56
|
+
});
|
|
57
|
+
return (await probe.exited) === 0;
|
|
58
|
+
} catch {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function gstElements(force = false): Promise<GstElements> {
|
|
64
|
+
if (cachedElements && !force) return cachedElements;
|
|
65
|
+
const launch = Bun.which("gst-launch-1.0");
|
|
66
|
+
if (!launch) {
|
|
67
|
+
cachedElements = { launch: null, pipewiresrc: false, vapostproc: false, vah264enc: false, x264enc: false };
|
|
68
|
+
return cachedElements;
|
|
69
|
+
}
|
|
70
|
+
const [pipewiresrc, vapostproc, vah264enc, x264enc] = await Promise.all([
|
|
71
|
+
hasElement("pipewiresrc"),
|
|
72
|
+
hasElement("vapostproc"),
|
|
73
|
+
hasElement("vah264enc"),
|
|
74
|
+
hasElement("x264enc"),
|
|
75
|
+
]);
|
|
76
|
+
cachedElements = { launch, pipewiresrc, vapostproc, vah264enc, x264enc };
|
|
77
|
+
return cachedElements;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Drop the probe cache, so a package installed while PPM is running is picked up. */
|
|
81
|
+
export function _resetGstElements(): void {
|
|
82
|
+
cachedElements = null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** `"4M"` / `"1500k"` / `"800000"` → kbps, which is what every GStreamer encoder takes.
|
|
86
|
+
* ffmpeg's string form is kept in `QualityPreset` because the ffmpeg backend passes it
|
|
87
|
+
* straight through; this is the one place it has to be understood. */
|
|
88
|
+
export function bitrateKbps(bitrate: string): number {
|
|
89
|
+
const m = /^(\d+(?:\.\d+)?)\s*([kKmM]?)$/.exec(bitrate.trim());
|
|
90
|
+
if (!m) return 1_000;
|
|
91
|
+
const n = Number(m[1]);
|
|
92
|
+
const unit = (m[2] ?? "").toLowerCase();
|
|
93
|
+
const kbps = unit === "m" ? n * 1_000 : unit === "k" ? n : n / 1_000;
|
|
94
|
+
// vah264enc rejects 0 outright, the same way ffmpeg does.
|
|
95
|
+
return Math.max(1, Math.round(kbps));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* The capture pipeline as an argv. Pure, so the element choices and the frame-rate cap can be
|
|
100
|
+
* asserted without a compositor, a GPU or a portal.
|
|
101
|
+
*/
|
|
102
|
+
export function buildWaylandCaptureArgs(
|
|
103
|
+
nodeId: number,
|
|
104
|
+
els: GstElements,
|
|
105
|
+
preset: QualityPreset = { fps: DEFAULT_FPS, bitrate: "1M" },
|
|
106
|
+
): string[] {
|
|
107
|
+
const fps = Math.max(1, Math.round(preset.fps || DEFAULT_FPS));
|
|
108
|
+
const kbps = bitrateKbps(preset.bitrate);
|
|
109
|
+
const launch = els.launch ?? "gst-launch-1.0";
|
|
110
|
+
|
|
111
|
+
// GPU path: vapostproc converts into VA memory that vah264enc encodes from without ever
|
|
112
|
+
// touching system RAM. Mixing the two halves (vapostproc + x264enc) would download the frame
|
|
113
|
+
// again, so the conversion follows the encoder rather than being chosen on its own.
|
|
114
|
+
const gpu = els.vapostproc && els.vah264enc;
|
|
115
|
+
const convert = gpu
|
|
116
|
+
? ["vapostproc", "!", "video/x-raw(memory:VAMemory),format=NV12"]
|
|
117
|
+
: ["videoconvert", "!", "video/x-raw,format=I420"];
|
|
118
|
+
const encode = gpu
|
|
119
|
+
? ["vah264enc", `bitrate=${kbps}`, `key-int-max=${fps}`]
|
|
120
|
+
// `tune=zerolatency` + `speed-preset=veryfast` are x264enc's equivalents of what
|
|
121
|
+
// `captureEncoderArgs` passes ffmpeg; without them the software path buffers whole
|
|
122
|
+
// B-frame groups and the felt lag doubles.
|
|
123
|
+
: ["x264enc", `bitrate=${kbps}`, `key-int-max=${fps}`, "tune=zerolatency", "speed-preset=veryfast"];
|
|
124
|
+
|
|
125
|
+
return [
|
|
126
|
+
launch, "-q",
|
|
127
|
+
"pipewiresrc", `path=${nodeId}`,
|
|
128
|
+
"!", "video/x-raw",
|
|
129
|
+
// Load-bearing twice over: caps the rate (max-rate= does not), and duplicates the last
|
|
130
|
+
// frame on a still screen so a late viewer is not left with a blank canvas. See the header.
|
|
131
|
+
"!", "videorate",
|
|
132
|
+
"!", `video/x-raw,framerate=${fps}/1`,
|
|
133
|
+
"!", ...convert,
|
|
134
|
+
"!", ...encode,
|
|
135
|
+
"!", "h264parse",
|
|
136
|
+
// Annex-B with one buffer per access unit — exactly the shape `AccessUnitAssembler` and the
|
|
137
|
+
// RTSP remux both expect, and the same bytes ffmpeg's `-f h264 pipe:1` produces.
|
|
138
|
+
"!", "video/x-h264,stream-format=byte-stream,alignment=au",
|
|
139
|
+
"!", "fdsink", "fd=1",
|
|
140
|
+
];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** ffmpeg's half of the WebRTC path: it only remuxes, never re-encodes, so this costs almost
|
|
144
|
+
* nothing. GStreamer would do it in-process with `rtspclientsink`, but that element lives in
|
|
145
|
+
* `gst-rtsp-server` which is a far less common package than `gst-plugin-pipewire` — requiring
|
|
146
|
+
* it would turn a working host into an unmet requirement for no gain. */
|
|
147
|
+
export function buildWaylandPublishArgs(ffmpeg: string, publishUrl: string): string[] {
|
|
148
|
+
return [
|
|
149
|
+
ffmpeg, "-hide_banner", "-loglevel", "error", "-nostdin",
|
|
150
|
+
"-fflags", "nobuffer", "-flags", "low_delay",
|
|
151
|
+
"-f", "h264", "-i", "pipe:0",
|
|
152
|
+
"-c:v", "copy",
|
|
153
|
+
"-flush_packets", "1",
|
|
154
|
+
"-f", "rtsp", "-rtsp_transport", "tcp", publishUrl,
|
|
155
|
+
];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export interface WaylandCaptureOptions extends StartCaptureOptions {
|
|
159
|
+
session: LinuxSession & { kind: "wayland" };
|
|
160
|
+
/** ffmpeg path, needed only for the RTSP remux on the WebRTC path. */
|
|
161
|
+
ffmpeg?: string | null;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** What a test stands in for: the portal is a D-Bus service, and the element probe resolves
|
|
165
|
+
* through the PATH this process started with, which a test cannot change. */
|
|
166
|
+
export interface WaylandCaptureDeps {
|
|
167
|
+
startPortal?: typeof startPortalScreenCast;
|
|
168
|
+
elements?: () => Promise<GstElements>;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Start Wayland capture. The portal session is opened first and torn down last: the PipeWire
|
|
173
|
+
* node belongs to the D-Bus connection that asked for it, so closing it before GStreamer has
|
|
174
|
+
* exited pulls the source out from under a running pipeline.
|
|
175
|
+
*/
|
|
176
|
+
export async function startWaylandCapture(
|
|
177
|
+
opts: WaylandCaptureOptions,
|
|
178
|
+
{ startPortal = startPortalScreenCast, elements = gstElements }: WaylandCaptureDeps = {},
|
|
179
|
+
): Promise<CaptureHandle> {
|
|
180
|
+
const els = await elements();
|
|
181
|
+
if (!els.launch) {
|
|
182
|
+
throw new CaptureUnavailableError("gst-launch-1.0 is not installed (Wayland capture needs GStreamer)");
|
|
183
|
+
}
|
|
184
|
+
if (!els.pipewiresrc) {
|
|
185
|
+
throw new CaptureUnavailableError("the GStreamer PipeWire plugin is missing (gst-plugin-pipewire)");
|
|
186
|
+
}
|
|
187
|
+
if (!els.vah264enc && !els.x264enc) {
|
|
188
|
+
throw new CaptureUnavailableError("no GStreamer H.264 encoder (install gst-plugins-ugly for x264enc)");
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const publishUrl = opts.publishUrl ?? null;
|
|
192
|
+
if (!publishUrl && !opts.onAccessUnit) {
|
|
193
|
+
throw new CaptureUnavailableError("startWaylandCapture needs either onAccessUnit or publishUrl");
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
let portal: PortalScreenCast;
|
|
197
|
+
try {
|
|
198
|
+
portal = await startPortal({ drawMouse: opts.drawMouse ?? true });
|
|
199
|
+
} catch (e) {
|
|
200
|
+
// A dismissed dialog or a missing portal is an unmet requirement, not a crash — surface the
|
|
201
|
+
// portal's own wording, which already says which of the two it was.
|
|
202
|
+
throw new CaptureUnavailableError((e as Error).message);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const env = { ...process.env, ...linuxSessionEnv(opts.session) };
|
|
206
|
+
const argv = buildWaylandCaptureArgs(portal.nodeId, els, opts.preset);
|
|
207
|
+
const gst = Bun.spawn(argv, { stdout: "pipe", stderr: "pipe", stdin: "ignore", env });
|
|
208
|
+
|
|
209
|
+
// On the WebRTC path a second process remuxes GStreamer's Annex-B into RTSP, and PPM carries
|
|
210
|
+
// the bytes between the two itself (the pump below). Handing `gst.stdout` over as the remux's
|
|
211
|
+
// stdin looks like a pipe between two children and is not one: measured, Bun reads every byte
|
|
212
|
+
// into this process and writes it back out with a pump of its own. When the remux dies, that
|
|
213
|
+
// pump rejects with an EPIPE nothing can catch — three of those in a minute make the server
|
|
214
|
+
// exit (`handleFatalError`) — and gst, still being read, never learns it lost its reader.
|
|
215
|
+
const remux = publishUrl && opts.ffmpeg
|
|
216
|
+
? Bun.spawn(buildWaylandPublishArgs(opts.ffmpeg, publishUrl), {
|
|
217
|
+
stdin: "pipe", stdout: "ignore", stderr: "pipe", env,
|
|
218
|
+
})
|
|
219
|
+
: null;
|
|
220
|
+
if (publishUrl && !remux) {
|
|
221
|
+
gst.kill("SIGKILL");
|
|
222
|
+
portal.stop();
|
|
223
|
+
throw new CaptureUnavailableError("ffmpeg is needed to publish the Wayland stream to the relay");
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
let stopped = false;
|
|
227
|
+
const stop = () => {
|
|
228
|
+
if (stopped) return;
|
|
229
|
+
stopped = true;
|
|
230
|
+
// gst-launch answers SIGINT with a clean EOS; the relay half is a plain remux and can be
|
|
231
|
+
// killed outright. The portal session goes last, after both readers are gone.
|
|
232
|
+
if (!gst.killed) gst.kill("SIGINT");
|
|
233
|
+
if (remux && !remux.killed) remux.kill("SIGKILL");
|
|
234
|
+
portal.stop();
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
// The first process to exit without being asked decides what the session is told, and takes
|
|
238
|
+
// the other one down with it: a dead remux leaves gst encoding for nobody, a dead gst leaves
|
|
239
|
+
// the remux publishing nothing. Either way the viewer would sit on a frozen picture.
|
|
240
|
+
const tail = (text: string) => text.trim().split("\n").slice(-3).join(" | ");
|
|
241
|
+
const gstErr = new Response(gst.stderr).text().catch(() => "");
|
|
242
|
+
const remuxErr = remux ? new Response(remux.stderr).text().catch(() => "") : Promise.resolve("");
|
|
243
|
+
let failure: Promise<{ code: number | null; reason?: string }> | null = null;
|
|
244
|
+
gst.exited.then((code) => {
|
|
245
|
+
if (stopped) return;
|
|
246
|
+
failure = (async () => {
|
|
247
|
+
if (code === 0 || code === null) return { code };
|
|
248
|
+
const reason = tail(await gstErr);
|
|
249
|
+
console.warn(`[remote-desktop] gst-launch exited ${code}: ${reason}`);
|
|
250
|
+
return { code, reason };
|
|
251
|
+
})();
|
|
252
|
+
stop();
|
|
253
|
+
});
|
|
254
|
+
remux?.exited.then((code) => {
|
|
255
|
+
if (stopped) return;
|
|
256
|
+
failure = (async () => {
|
|
257
|
+
// Any exit nobody asked for is a failure here, a clean one included: it means the relay
|
|
258
|
+
// stopped taking the stream.
|
|
259
|
+
const reason = tail(await remuxErr) || `the relay publisher exited with code ${code}`;
|
|
260
|
+
console.warn(`[remote-desktop] relay remux exited ${code}: ${reason}`);
|
|
261
|
+
return { code, reason };
|
|
262
|
+
})();
|
|
263
|
+
stop();
|
|
264
|
+
});
|
|
265
|
+
// Reported once both are gone, so a respawn waiting on it never overlaps the next capture
|
|
266
|
+
// with a process left over from this one.
|
|
267
|
+
void Promise.all([gst.exited, remux?.exited]).then(async ([gstCode]) => {
|
|
268
|
+
portal.stop();
|
|
269
|
+
const failed = failure ? await failure : null;
|
|
270
|
+
opts.onExit?.(failed ? failed.code : gstCode, failed?.reason);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
if (remux) {
|
|
274
|
+
// One write and one flush per chunk, both awaited: an unawaited write that meets a dead
|
|
275
|
+
// remux rejects with nobody to catch it, which is the failure this pump exists to avoid.
|
|
276
|
+
// Never a cancel, for the same reason as the pull loop below.
|
|
277
|
+
const source = gst.stdout.getReader();
|
|
278
|
+
(async () => {
|
|
279
|
+
for (;;) {
|
|
280
|
+
const { done, value } = await source.read();
|
|
281
|
+
if (done) break;
|
|
282
|
+
if (!value) continue;
|
|
283
|
+
await remux.stdin.write(value);
|
|
284
|
+
await remux.stdin.flush();
|
|
285
|
+
}
|
|
286
|
+
await remux.stdin.end();
|
|
287
|
+
})().catch(() => {
|
|
288
|
+
// The remux is gone, and its exit above is what reports it. Kill it anyway: a pump that
|
|
289
|
+
// failed with the remux still running would leave gst blocked on a full pipe forever.
|
|
290
|
+
try { remux.kill("SIGKILL"); } catch { /* already gone */ }
|
|
291
|
+
});
|
|
292
|
+
return { cachedSps: () => null, stop, isStopped: () => stopped };
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const assembler = new AccessUnitAssembler();
|
|
296
|
+
const onAccessUnit = opts.onAccessUnit!;
|
|
297
|
+
// Manual pull loop, never a cancel — same constraint as the ffmpeg backend (see that file's
|
|
298
|
+
// header: cancelling this reader segfaults Bun on Windows, and the shape must not diverge).
|
|
299
|
+
const reader = gst.stdout.getReader();
|
|
300
|
+
(async () => {
|
|
301
|
+
for (;;) {
|
|
302
|
+
const { done, value } = await reader.read();
|
|
303
|
+
if (done) return;
|
|
304
|
+
if (!value) continue;
|
|
305
|
+
for (const au of assembler.push(value)) onAccessUnit(au);
|
|
306
|
+
}
|
|
307
|
+
})().catch((e) => {
|
|
308
|
+
console.error(`[remote-desktop] wayland capture pump failed: ${(e as Error).message}`);
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
return { cachedSps: () => assembler.cachedSps(), stop, isStopped: () => stopped };
|
|
312
|
+
}
|
|
@@ -39,6 +39,7 @@ export function buildCaptureArgs(
|
|
|
39
39
|
input: CaptureInput = { kind: "gdigrab" },
|
|
40
40
|
preset: QualityPreset = { fps: DEFAULT_FPS, bitrate: "1M" },
|
|
41
41
|
drawMouse = true,
|
|
42
|
+
publishUrl: string | null = null,
|
|
42
43
|
): string[] {
|
|
43
44
|
return [
|
|
44
45
|
ffmpeg, "-hide_banner", "-loglevel", "error", "-nostdin",
|
|
@@ -51,10 +52,27 @@ export function buildCaptureArgs(
|
|
|
51
52
|
...withVideoFilter(captureVideoFilter(input, encoder, preset)),
|
|
52
53
|
...captureEncoderArgs(encoder, preset),
|
|
53
54
|
"-flush_packets", "1",
|
|
54
|
-
|
|
55
|
+
...captureOutputArgs(publishUrl),
|
|
55
56
|
];
|
|
56
57
|
}
|
|
57
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Where the encoded H.264 goes: PPM's own WebSocket path reads it from `pipe:1`, the WebRTC
|
|
61
|
+
* path pushes it into the local MediaMTX relay instead.
|
|
62
|
+
*
|
|
63
|
+
* Everything before this point is byte-identical between the two, which is the whole design:
|
|
64
|
+
* the relay repackages what ffmpeg already encoded and never re-encodes, so the quality ladder,
|
|
65
|
+
* the cursor flag, privacy mode and every per-platform grabber keep working unchanged.
|
|
66
|
+
*
|
|
67
|
+
* TCP, not the default UDP: the relay is on loopback where there is nothing to gain from UDP,
|
|
68
|
+
* and a UDP push silently drops packets at MTU boundaries under load where TCP simply blocks.
|
|
69
|
+
*/
|
|
70
|
+
function captureOutputArgs(publishUrl: string | null): string[] {
|
|
71
|
+
return publishUrl
|
|
72
|
+
? ["-f", "rtsp", "-rtsp_transport", "tcp", publishUrl]
|
|
73
|
+
: ["-f", "h264", "pipe:1"];
|
|
74
|
+
}
|
|
75
|
+
|
|
58
76
|
function withVideoFilter(filter: string): string[] {
|
|
59
77
|
return filter ? ["-vf", filter] : [];
|
|
60
78
|
}
|
|
@@ -81,7 +99,15 @@ export interface StartCaptureOptions {
|
|
|
81
99
|
* `workingEncoders()` reported: an encoder this build/GPU cannot run makes ffmpeg exit at
|
|
82
100
|
* once, which the session then reports as "Capture failed". */
|
|
83
101
|
encoder?: string;
|
|
84
|
-
|
|
102
|
+
/**
|
|
103
|
+
* Push the encoded stream into this RTSP URL (the local MediaMTX relay) instead of reading
|
|
104
|
+
* it back over `pipe:1`. When set there is no stdout to parse, so `onAccessUnit` is never
|
|
105
|
+
* called and `cachedSps()` answers null — the browser learns the codec from the SDP the
|
|
106
|
+
* relay answers with, not from PPM's own SPS parse.
|
|
107
|
+
*/
|
|
108
|
+
publishUrl?: string | null;
|
|
109
|
+
/** Required for the WebSocket path; ignored when `publishUrl` is set. */
|
|
110
|
+
onAccessUnit?: (au: AccessUnit) => void;
|
|
85
111
|
/** Called once the process exits, whether via `stop()` or on its own (crash/killed
|
|
86
112
|
* externally) — lets the session registry clean up without polling. `reason` is a short
|
|
87
113
|
* stderr tail, present only when ffmpeg died on its own (e.g. gdigrab access denied on a
|
|
@@ -98,17 +124,37 @@ export async function startCapture(opts: StartCaptureOptions): Promise<CaptureHa
|
|
|
98
124
|
// Linux only: the grabber is chosen by session type, and ffmpeg is handed the session's
|
|
99
125
|
// DISPLAY/XAUTHORITY because the PPM process very often has neither of its own.
|
|
100
126
|
const session = process.platform === "linux" ? detectLinuxSession() : null;
|
|
127
|
+
|
|
128
|
+
// Wayland has no ffmpeg grabber at all: the compositor only shares the screen through the
|
|
129
|
+
// desktop portal, and ffmpeg can read the PipeWire node it hands back only when built
|
|
130
|
+
// `--enable-libpipewire`, which distributions do not do. That backend is GStreamer-based and
|
|
131
|
+
// lives in its own module; it produces the same Annex-B stdout, so nothing downstream differs.
|
|
132
|
+
// The import is dynamic to keep the two files out of a require cycle — the Wayland module
|
|
133
|
+
// needs `CaptureUnavailableError` and the option types from here.
|
|
134
|
+
if (session?.kind === "wayland") {
|
|
135
|
+
const { startWaylandCapture } = await import("./remote-desktop-capture-wayland.ts");
|
|
136
|
+
return startWaylandCapture({ ...opts, session, ffmpeg: caps.ffmpeg ?? null });
|
|
137
|
+
}
|
|
138
|
+
|
|
101
139
|
const d = opts.display;
|
|
102
140
|
const rect: CaptureRect | null =
|
|
103
141
|
d && d.width > 0 && d.height > 0 ? { x: d.x, y: d.y, width: d.width, height: d.height } : null;
|
|
104
142
|
const input = captureInputForPlatform(process.platform, d?.captureIndex ?? 0, { session, rect });
|
|
105
143
|
if (!input) throw new CaptureUnavailableError(`no screen capture input on ${process.platform}`);
|
|
106
144
|
|
|
145
|
+
const publishUrl = opts.publishUrl ?? null;
|
|
146
|
+
if (!publishUrl && !opts.onAccessUnit) {
|
|
147
|
+
throw new CaptureUnavailableError("startCapture needs either onAccessUnit or publishUrl");
|
|
148
|
+
}
|
|
107
149
|
const argv = buildCaptureArgs(
|
|
108
150
|
caps.ffmpeg, opts.encoder ?? caps.encoder ?? "libx264", input, opts.preset,
|
|
109
|
-
opts.drawMouse ?? true,
|
|
151
|
+
opts.drawMouse ?? true, publishUrl,
|
|
110
152
|
);
|
|
111
153
|
const proc = Bun.spawn(argv, {
|
|
154
|
+
// Always a pipe, in both modes. Making it conditional widens Bun's spawn type so that
|
|
155
|
+
// `proc.stdout` is possibly-undefined everywhere below, and the RTSP mode writes nothing
|
|
156
|
+
// to stdout anyway (ffmpeg logs to stderr) — it is drained rather than left unread so a
|
|
157
|
+
// future log line on stdout cannot block the encoder.
|
|
112
158
|
stdout: "pipe",
|
|
113
159
|
stderr: "pipe",
|
|
114
160
|
stdin: "ignore",
|
|
@@ -145,7 +191,17 @@ export async function startCapture(opts: StartCaptureOptions): Promise<CaptureHa
|
|
|
145
191
|
opts.onExit?.(code, reason);
|
|
146
192
|
});
|
|
147
193
|
|
|
194
|
+
// The relay reads the stream over RTSP, so there is nothing to assemble here — just drain
|
|
195
|
+
// stdout with the same manual pull loop (never a cancel; see the file header).
|
|
196
|
+
if (publishUrl) {
|
|
197
|
+
const idle = proc.stdout.getReader();
|
|
198
|
+
(async () => { for (;;) { const { done } = await idle.read(); if (done) return; } })()
|
|
199
|
+
.catch(() => { /* the process is gone; stop() has already run or is about to */ });
|
|
200
|
+
return { cachedSps: () => null, stop, isStopped: () => stopped };
|
|
201
|
+
}
|
|
202
|
+
|
|
148
203
|
const assembler = new AccessUnitAssembler();
|
|
204
|
+
const onAccessUnit = opts.onAccessUnit!;
|
|
149
205
|
|
|
150
206
|
// Manual pull loop — the only safe way to drain this stdout (see file header). Runs
|
|
151
207
|
// detached from the caller; it terminates itself once `proc.kill()` resolves the pending
|
|
@@ -156,7 +212,7 @@ export async function startCapture(opts: StartCaptureOptions): Promise<CaptureHa
|
|
|
156
212
|
const { done, value } = await reader.read();
|
|
157
213
|
if (done) return;
|
|
158
214
|
if (!value) continue;
|
|
159
|
-
for (const au of assembler.push(value))
|
|
215
|
+
for (const au of assembler.push(value)) onAccessUnit(au);
|
|
160
216
|
}
|
|
161
217
|
})().catch((e) => {
|
|
162
218
|
console.error(`[remote-desktop] capture pump failed: ${(e as Error).message}`);
|