@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,255 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebRTC (WHEP) video painted into the same `<canvas>` the WebCodecs path draws to.
|
|
3
|
+
*
|
|
4
|
+
* Painting into the canvas rather than showing a `<video>` is deliberate: every other part of
|
|
5
|
+
* the viewer — the scale modes in `remote-desktop-view-style.ts`, coordinate mapping in
|
|
6
|
+
* `remote-desktop-coords.ts`, the `MediaRecorder` in `use-remote-desktop-recorder.ts`, the
|
|
7
|
+
* stats overlay — is built on one canvas, and giving it a second rendering surface to reason
|
|
8
|
+
* about would touch all of them for no user-visible gain. The cost is one hardware-accelerated
|
|
9
|
+
* `drawImage` per frame.
|
|
10
|
+
*
|
|
11
|
+
* Why this path exists at all: `VideoDecoder` is **secure-context only**. Measured in one
|
|
12
|
+
* browser at one moment, it is `undefined` on `http://192.168.98.96:3210` and present on
|
|
13
|
+
* `http://127.0.0.1:3210` — so the WebCodecs viewer cannot start on a plain-HTTP LAN origin,
|
|
14
|
+
* which is how PPM is most often reached from a phone or tablet. `RTCPeerConnection` has no
|
|
15
|
+
* such restriction and works on both.
|
|
16
|
+
*
|
|
17
|
+
* The handshake is non-trickle: ICE gathering is allowed to finish before the offer is posted,
|
|
18
|
+
* so one request carries every candidate and there is no PATCH channel to keep open. It costs
|
|
19
|
+
* a few hundred milliseconds at connect and removes a whole signalling path.
|
|
20
|
+
*/
|
|
21
|
+
import { useCallback, useRef, useState } from "react";
|
|
22
|
+
import { getAuthToken } from "@/lib/api-client";
|
|
23
|
+
import type { DecoderStatus } from "./use-h264-canvas-decoder";
|
|
24
|
+
|
|
25
|
+
/** MediaMTX answers 404 until the publisher's first packet lands, and a quality change
|
|
26
|
+
* respawns that publisher — so the handshake retries rather than failing the viewer. */
|
|
27
|
+
const WHEP_RETRY_MS = 400;
|
|
28
|
+
const WHEP_RETRY_LIMIT = 25; // ~10s, comfortably past ffmpeg's ~400ms respawn
|
|
29
|
+
|
|
30
|
+
/** How long ICE gets once the answer is in. The media is UDP straight to the host's interface
|
|
31
|
+
* addresses, which a viewer on the Cloudflare tunnel has no route to — and measured in
|
|
32
|
+
* Chromium, with no candidate reachable `connectionState` only turns `failed` after 15 s, a
|
|
33
|
+
* quarter-minute of black screen before the caller can try anything else. A path that works
|
|
34
|
+
* connects long before this. */
|
|
35
|
+
export const CONNECT_DEADLINE_MS = 6_000;
|
|
36
|
+
|
|
37
|
+
export interface UseWebrtcCanvasVideoOptions {
|
|
38
|
+
/** Called at most once per `start()`, when this path cannot carry the picture: the browser has
|
|
39
|
+
* no WebRTC, the handshake was refused or never produced a stream, ICE did not connect within
|
|
40
|
+
* `CONNECT_DEADLINE_MS`, or a connection that was up failed. */
|
|
41
|
+
onFailure?: () => void;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface UseWebrtcCanvasVideoResult {
|
|
45
|
+
status: DecoderStatus;
|
|
46
|
+
errorMessage: string | null;
|
|
47
|
+
/** Run (or re-run) the WHEP handshake against a PPM path such as
|
|
48
|
+
* `/api/remote-desktop/whep/<ticket>`. Safe to call again on a respawn. */
|
|
49
|
+
start: (whepPath: string) => void;
|
|
50
|
+
stop: () => void;
|
|
51
|
+
getFrameCount: () => number;
|
|
52
|
+
/** Bytes received on the video track, for the stats overlay. WebRTC counts these for us. */
|
|
53
|
+
getTotalBytes: () => number;
|
|
54
|
+
frameSize: { width: number; height: number };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function useWebrtcCanvasVideo(
|
|
58
|
+
canvasRef: React.RefObject<HTMLCanvasElement | null>,
|
|
59
|
+
{ onFailure }: UseWebrtcCanvasVideoOptions = {},
|
|
60
|
+
): UseWebrtcCanvasVideoResult {
|
|
61
|
+
const [status, setStatus] = useState<DecoderStatus>("idle");
|
|
62
|
+
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
|
63
|
+
const [frameSize, setFrameSize] = useState({ width: 0, height: 0 });
|
|
64
|
+
const pcRef = useRef<RTCPeerConnection | null>(null);
|
|
65
|
+
const videoRef = useRef<HTMLVideoElement | null>(null);
|
|
66
|
+
const ctxRef = useRef<CanvasRenderingContext2D | null>(null);
|
|
67
|
+
const frameCountRef = useRef(0);
|
|
68
|
+
const bytesRef = useRef(0);
|
|
69
|
+
const statsTimerRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
|
70
|
+
const deadlineRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
71
|
+
const generationRef = useRef(0);
|
|
72
|
+
// A ref, so a new callback each render does not make `start` a new function.
|
|
73
|
+
const onFailureRef = useRef(onFailure);
|
|
74
|
+
onFailureRef.current = onFailure;
|
|
75
|
+
|
|
76
|
+
const teardown = useCallback(() => {
|
|
77
|
+
generationRef.current += 1;
|
|
78
|
+
if (statsTimerRef.current) { clearInterval(statsTimerRef.current); statsTimerRef.current = null; }
|
|
79
|
+
if (deadlineRef.current) { clearTimeout(deadlineRef.current); deadlineRef.current = null; }
|
|
80
|
+
try { pcRef.current?.close(); } catch { /* already closed */ }
|
|
81
|
+
pcRef.current = null;
|
|
82
|
+
const video = videoRef.current;
|
|
83
|
+
if (video) {
|
|
84
|
+
video.srcObject = null;
|
|
85
|
+
video.remove();
|
|
86
|
+
videoRef.current = null;
|
|
87
|
+
}
|
|
88
|
+
ctxRef.current = null;
|
|
89
|
+
}, []);
|
|
90
|
+
|
|
91
|
+
const stop = useCallback(() => {
|
|
92
|
+
teardown();
|
|
93
|
+
setStatus("idle");
|
|
94
|
+
setErrorMessage(null);
|
|
95
|
+
}, [teardown]);
|
|
96
|
+
|
|
97
|
+
const start = useCallback((whepPath: string) => {
|
|
98
|
+
teardown();
|
|
99
|
+
const generation = generationRef.current;
|
|
100
|
+
const stale = () => generation !== generationRef.current;
|
|
101
|
+
let failed = false;
|
|
102
|
+
const fail = (message: string, kind: DecoderStatus = "error") => {
|
|
103
|
+
if (stale() || failed) return;
|
|
104
|
+
failed = true;
|
|
105
|
+
setStatus(kind);
|
|
106
|
+
setErrorMessage(message);
|
|
107
|
+
onFailureRef.current?.();
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
if (typeof RTCPeerConnection === "undefined") {
|
|
111
|
+
fail("This browser has no WebRTC support.", "unsupported");
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Hidden, but attached: a detached element is not guaranteed to run
|
|
116
|
+
// `requestVideoFrameCallback`, and `display:none` lets a browser stop decoding entirely.
|
|
117
|
+
const video = document.createElement("video");
|
|
118
|
+
video.autoplay = true;
|
|
119
|
+
video.muted = true; // host audio has its own path; a muted element may autoplay
|
|
120
|
+
video.playsInline = true;
|
|
121
|
+
video.setAttribute("aria-hidden", "true");
|
|
122
|
+
video.style.cssText =
|
|
123
|
+
"position:fixed;top:0;left:0;width:1px;height:1px;opacity:0;pointer-events:none";
|
|
124
|
+
document.body.appendChild(video);
|
|
125
|
+
videoRef.current = video;
|
|
126
|
+
|
|
127
|
+
const paint = () => {
|
|
128
|
+
if (stale()) return;
|
|
129
|
+
const canvas = canvasRef.current;
|
|
130
|
+
if (canvas && video.videoWidth > 0) {
|
|
131
|
+
if (canvas.width !== video.videoWidth || canvas.height !== video.videoHeight) {
|
|
132
|
+
canvas.width = video.videoWidth;
|
|
133
|
+
canvas.height = video.videoHeight;
|
|
134
|
+
// Inside the size guard: one setState per resolution change, not per frame.
|
|
135
|
+
setFrameSize({ width: video.videoWidth, height: video.videoHeight });
|
|
136
|
+
}
|
|
137
|
+
if (!ctxRef.current) ctxRef.current = canvas.getContext("2d");
|
|
138
|
+
ctxRef.current?.drawImage(video, 0, 0, canvas.width, canvas.height);
|
|
139
|
+
frameCountRef.current += 1;
|
|
140
|
+
}
|
|
141
|
+
schedule();
|
|
142
|
+
};
|
|
143
|
+
// `requestVideoFrameCallback` fires once per *decoded* frame, so the canvas never repaints
|
|
144
|
+
// a picture that has not changed. rAF is the fallback for browsers without it and repaints
|
|
145
|
+
// at display rate instead, which is wasteful but correct.
|
|
146
|
+
const schedule = () =>
|
|
147
|
+
typeof video.requestVideoFrameCallback === "function"
|
|
148
|
+
? video.requestVideoFrameCallback(() => paint())
|
|
149
|
+
: requestAnimationFrame(() => paint());
|
|
150
|
+
|
|
151
|
+
const pc = new RTCPeerConnection({ iceServers: [] });
|
|
152
|
+
pcRef.current = pc;
|
|
153
|
+
pc.addTransceiver("video", { direction: "recvonly" });
|
|
154
|
+
pc.ontrack = (e) => {
|
|
155
|
+
const stream = new MediaStream();
|
|
156
|
+
stream.addTrack(e.track);
|
|
157
|
+
video.srcObject = stream;
|
|
158
|
+
void video.play().catch(() => { /* autoplay is allowed for a muted element */ });
|
|
159
|
+
schedule();
|
|
160
|
+
};
|
|
161
|
+
pc.onconnectionstatechange = () => {
|
|
162
|
+
if (stale()) return;
|
|
163
|
+
if (pc.connectionState === "connected") {
|
|
164
|
+
if (deadlineRef.current) { clearTimeout(deadlineRef.current); deadlineRef.current = null; }
|
|
165
|
+
setStatus("ready");
|
|
166
|
+
setErrorMessage(null);
|
|
167
|
+
}
|
|
168
|
+
if (pc.connectionState === "failed") fail("The WebRTC connection failed.");
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
statsTimerRef.current = setInterval(async () => {
|
|
172
|
+
if (stale() || !pcRef.current) return;
|
|
173
|
+
try {
|
|
174
|
+
const report = await pcRef.current.getStats();
|
|
175
|
+
report.forEach((r: any) => {
|
|
176
|
+
if (r.type === "inbound-rtp" && r.kind === "video") bytesRef.current = r.bytesReceived ?? 0;
|
|
177
|
+
});
|
|
178
|
+
} catch { /* the connection is going away */ }
|
|
179
|
+
}, 1000);
|
|
180
|
+
|
|
181
|
+
void (async () => {
|
|
182
|
+
try {
|
|
183
|
+
await pc.setLocalDescription(await pc.createOffer());
|
|
184
|
+
await waitForIceGathering(pc);
|
|
185
|
+
if (stale()) return;
|
|
186
|
+
|
|
187
|
+
const token = getAuthToken();
|
|
188
|
+
const answer = await postOfferWithRetry(whepPath, pc.localDescription!.sdp, token, stale);
|
|
189
|
+
if (stale()) return;
|
|
190
|
+
if (!answer) {
|
|
191
|
+
fail("The host's WebRTC relay never produced a stream.");
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
await pc.setRemoteDescription({ type: "answer", sdp: answer });
|
|
195
|
+
if (stale() || pc.connectionState === "connected") return;
|
|
196
|
+
deadlineRef.current = setTimeout(() => {
|
|
197
|
+
deadlineRef.current = null;
|
|
198
|
+
if (pc.connectionState !== "connected") fail("The WebRTC connection did not come up.");
|
|
199
|
+
}, CONNECT_DEADLINE_MS);
|
|
200
|
+
} catch (e) {
|
|
201
|
+
fail((e as Error).message);
|
|
202
|
+
}
|
|
203
|
+
})();
|
|
204
|
+
}, [canvasRef, teardown]);
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
status, errorMessage, start, stop,
|
|
208
|
+
getFrameCount: () => frameCountRef.current,
|
|
209
|
+
getTotalBytes: () => bytesRef.current,
|
|
210
|
+
frameSize,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** Resolve once the browser has gathered every candidate, so one POST carries them all. */
|
|
215
|
+
function waitForIceGathering(pc: RTCPeerConnection): Promise<void> {
|
|
216
|
+
if (pc.iceGatheringState === "complete") return Promise.resolve();
|
|
217
|
+
return new Promise((resolve) => {
|
|
218
|
+
const done = () => {
|
|
219
|
+
if (pc.iceGatheringState !== "complete") return;
|
|
220
|
+
pc.removeEventListener("icegatheringstatechange", done);
|
|
221
|
+
resolve();
|
|
222
|
+
};
|
|
223
|
+
pc.addEventListener("icegatheringstatechange", done);
|
|
224
|
+
// A candidate that never arrives must not hang the viewer; what has been gathered by then
|
|
225
|
+
// is enough on a LAN or a tailnet, where the host candidate is the one that wins anyway.
|
|
226
|
+
setTimeout(() => { pc.removeEventListener("icegatheringstatechange", done); resolve(); }, 2000);
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/** POST the offer, retrying while the relay has no publisher yet. Returns the SDP answer. */
|
|
231
|
+
async function postOfferWithRetry(
|
|
232
|
+
whepPath: string, offer: string, token: string | null, stale: () => boolean,
|
|
233
|
+
): Promise<string | null> {
|
|
234
|
+
for (let attempt = 0; attempt < WHEP_RETRY_LIMIT; attempt++) {
|
|
235
|
+
if (stale()) return null;
|
|
236
|
+
const res = await fetch(whepPath, {
|
|
237
|
+
method: "POST",
|
|
238
|
+
headers: {
|
|
239
|
+
"Content-Type": "application/sdp",
|
|
240
|
+
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
|
241
|
+
"x-ppm-client": "web",
|
|
242
|
+
},
|
|
243
|
+
body: offer,
|
|
244
|
+
});
|
|
245
|
+
if (res.ok) return await res.text();
|
|
246
|
+
// 404 is "the stream is not up yet" and is the expected answer for the first few hundred
|
|
247
|
+
// milliseconds. Anything else is a real refusal — auth, a dead ticket — and retrying it
|
|
248
|
+
// would just hide the reason.
|
|
249
|
+
if (res.status !== 404 && res.status !== 502) {
|
|
250
|
+
throw new Error(`the host refused the WebRTC handshake (${res.status})`);
|
|
251
|
+
}
|
|
252
|
+
await new Promise((r) => setTimeout(r, WHEP_RETRY_MS));
|
|
253
|
+
}
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import { Component, type ErrorInfo, type ReactNode } from "react";
|
|
18
18
|
import { isChunkLoadError, purgeAndReload } from "@/lib/chunk-recovery";
|
|
19
|
+
import { reportTraceError } from "@/lib/trace-client";
|
|
19
20
|
|
|
20
21
|
interface Props {
|
|
21
22
|
children: ReactNode;
|
|
@@ -84,6 +85,10 @@ export class RootErrorBoundary extends Component<Props, State> {
|
|
|
84
85
|
}
|
|
85
86
|
|
|
86
87
|
override componentDidCatch(error: Error, info: ErrorInfo) {
|
|
88
|
+
// To the server too: this screen is usually seen on a device with no devtools open.
|
|
89
|
+
reportTraceError(isChunkLoadError(error) ? "chunk_error" : "render_error", error, {
|
|
90
|
+
componentStack: info.componentStack ?? undefined,
|
|
91
|
+
});
|
|
87
92
|
console.error("[ppm] unhandled render error", error, info.componentStack);
|
|
88
93
|
}
|
|
89
94
|
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remote Desktop settings: the WebRTC relay, which is an optional download.
|
|
3
|
+
*
|
|
4
|
+
* Without it the viewer streams H.264 over the WebSocket and decodes with WebCodecs, which
|
|
5
|
+
* works — except that `VideoDecoder` is secure-context only, so on a plain-HTTP LAN origin
|
|
6
|
+
* (how a phone or tablet usually reaches PPM) it cannot start at all. With the relay installed
|
|
7
|
+
* the host serves WebRTC instead, which has no such restriction. Nothing else changes: the
|
|
8
|
+
* relay repackages the stream ffmpeg already encoded and never re-encodes it.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
12
|
+
import { toast } from "sonner";
|
|
13
|
+
import { Monitor, Loader2, Download, Trash2, Check } from "@/lib/icons";
|
|
14
|
+
import { Button } from "@/components/ui/button";
|
|
15
|
+
import { Separator } from "@/components/ui/separator";
|
|
16
|
+
import { api } from "@/lib/api-client";
|
|
17
|
+
|
|
18
|
+
interface RelayJob {
|
|
19
|
+
receivedBytes: number;
|
|
20
|
+
totalBytes: number;
|
|
21
|
+
done: boolean;
|
|
22
|
+
error: string | null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface RelayStatus {
|
|
26
|
+
installed: boolean;
|
|
27
|
+
path?: string;
|
|
28
|
+
source?: "bundled" | "system";
|
|
29
|
+
pinnedVersion: string;
|
|
30
|
+
/** False when the release publishes no build for this platform/architecture. */
|
|
31
|
+
available: boolean;
|
|
32
|
+
job: RelayJob | null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const mb = (bytes: number) => `${Math.round(bytes / 1e6)} MB`;
|
|
36
|
+
|
|
37
|
+
export function RemoteDesktopSettingsSection() {
|
|
38
|
+
const [status, setStatus] = useState<RelayStatus | null>(null);
|
|
39
|
+
const [busy, setBusy] = useState(false);
|
|
40
|
+
const pollRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
41
|
+
|
|
42
|
+
const load = useCallback(async () => {
|
|
43
|
+
try {
|
|
44
|
+
setStatus(await api.get<RelayStatus>("/api/remote-desktop/relay"));
|
|
45
|
+
} catch {
|
|
46
|
+
// A status that cannot be read leaves the pane on "not installed", which is the thing
|
|
47
|
+
// the user can act on anyway.
|
|
48
|
+
}
|
|
49
|
+
}, []);
|
|
50
|
+
|
|
51
|
+
useEffect(() => { void load(); }, [load]);
|
|
52
|
+
|
|
53
|
+
// Poll only while a download is running: the byte count is the only sign of progress.
|
|
54
|
+
const installing = !!status?.job && !status.job.done;
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
if (!installing) return;
|
|
57
|
+
pollRef.current = setTimeout(() => void load(), 700);
|
|
58
|
+
return () => { if (pollRef.current) clearTimeout(pollRef.current); };
|
|
59
|
+
}, [installing, status, load]);
|
|
60
|
+
|
|
61
|
+
const install = async () => {
|
|
62
|
+
setBusy(true);
|
|
63
|
+
try {
|
|
64
|
+
await api.post("/api/remote-desktop/relay/install");
|
|
65
|
+
await load();
|
|
66
|
+
} catch (e) {
|
|
67
|
+
toast.error("Could not start the download", {
|
|
68
|
+
description: e instanceof Error ? e.message : String(e),
|
|
69
|
+
});
|
|
70
|
+
} finally { setBusy(false); }
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const uninstall = async () => {
|
|
74
|
+
setBusy(true);
|
|
75
|
+
try {
|
|
76
|
+
await api.post("/api/remote-desktop/relay/uninstall");
|
|
77
|
+
await load();
|
|
78
|
+
} catch (e) {
|
|
79
|
+
toast.error("Could not remove the relay", {
|
|
80
|
+
description: e instanceof Error ? e.message : String(e),
|
|
81
|
+
});
|
|
82
|
+
} finally { setBusy(false); }
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const job = status?.job ?? null;
|
|
86
|
+
const percent = job && job.totalBytes > 0
|
|
87
|
+
? Math.round((job.receivedBytes / job.totalBytes) * 100)
|
|
88
|
+
: null;
|
|
89
|
+
const insecure = typeof window !== "undefined" && !window.isSecureContext;
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<div className="space-y-6">
|
|
93
|
+
<section className="space-y-3">
|
|
94
|
+
<div>
|
|
95
|
+
<h3 className="text-sm font-medium">WebRTC relay</h3>
|
|
96
|
+
<p className="text-xs text-muted-foreground leading-relaxed">
|
|
97
|
+
Streams the desktop over WebRTC instead of a WebSocket. It repackages the video this
|
|
98
|
+
host already encoded — it never re-encodes, so the picture and the CPU cost are
|
|
99
|
+
unchanged.
|
|
100
|
+
</p>
|
|
101
|
+
</div>
|
|
102
|
+
|
|
103
|
+
{insecure && (
|
|
104
|
+
// The reason most people will end up on this pane, so it says so plainly rather than
|
|
105
|
+
// leaving them to discover it as a broken viewer.
|
|
106
|
+
<p className="rounded-md bg-muted px-4 py-3 text-xs leading-relaxed">
|
|
107
|
+
You are reading this over plain HTTP. Browsers only expose the WebCodecs video
|
|
108
|
+
decoder on HTTPS or localhost, so <strong>Remote Desktop cannot show a picture on
|
|
109
|
+
this address</strong> without the relay. Installing it fixes that; so does reaching
|
|
110
|
+
PPM through its HTTPS tunnel.
|
|
111
|
+
</p>
|
|
112
|
+
)}
|
|
113
|
+
|
|
114
|
+
{!status?.available && !status?.installed && (
|
|
115
|
+
<p className="text-xs text-muted-foreground leading-relaxed">
|
|
116
|
+
MediaMTX publishes no build for this platform. You can install it yourself and put
|
|
117
|
+
<code className="mx-1 rounded bg-muted px-1 py-0.5">mediamtx</code>
|
|
118
|
+
on this host's PATH — PPM generates its own configuration either way.
|
|
119
|
+
</p>
|
|
120
|
+
)}
|
|
121
|
+
|
|
122
|
+
<div className="flex flex-col gap-3 sm:flex-row sm:items-center">
|
|
123
|
+
<div className="flex items-center gap-2 text-sm">
|
|
124
|
+
{status?.installed
|
|
125
|
+
? <Check className="size-4 text-emerald-500" />
|
|
126
|
+
: <Monitor className="size-4 text-muted-foreground" />}
|
|
127
|
+
<span>
|
|
128
|
+
{status?.installed
|
|
129
|
+
? status.source === "system"
|
|
130
|
+
? "Installed on this host (not by PPM)"
|
|
131
|
+
: `Installed — MediaMTX ${status.pinnedVersion}`
|
|
132
|
+
: "Not installed"}
|
|
133
|
+
</span>
|
|
134
|
+
</div>
|
|
135
|
+
|
|
136
|
+
<div className="flex flex-wrap gap-2 sm:ml-auto">
|
|
137
|
+
{!status?.installed && status?.available && (
|
|
138
|
+
<Button onClick={install} disabled={busy || installing} className="min-h-11 px-4">
|
|
139
|
+
{installing
|
|
140
|
+
? <><Loader2 className="size-4 animate-spin" />Downloading{percent !== null ? ` ${percent}%` : ""}</>
|
|
141
|
+
: <><Download className="size-4" />Install</>}
|
|
142
|
+
</Button>
|
|
143
|
+
)}
|
|
144
|
+
{status?.installed && status.source === "bundled" && (
|
|
145
|
+
<Button variant="outline" onClick={uninstall} disabled={busy || installing} className="min-h-11 px-4">
|
|
146
|
+
<Trash2 className="size-4" />Remove
|
|
147
|
+
</Button>
|
|
148
|
+
)}
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
|
|
152
|
+
{installing && job && (
|
|
153
|
+
<p className="text-xs text-muted-foreground">
|
|
154
|
+
{mb(job.receivedBytes)}{job.totalBytes > 0 ? ` of ${mb(job.totalBytes)}` : ""}
|
|
155
|
+
</p>
|
|
156
|
+
)}
|
|
157
|
+
{job?.error && <p className="text-xs text-destructive">{job.error}</p>}
|
|
158
|
+
{status?.path && (
|
|
159
|
+
<p className="text-xs text-muted-foreground break-all">{status.path}</p>
|
|
160
|
+
)}
|
|
161
|
+
</section>
|
|
162
|
+
|
|
163
|
+
<Separator />
|
|
164
|
+
|
|
165
|
+
<section className="space-y-2">
|
|
166
|
+
<h3 className="text-sm font-medium">What changes when it is on</h3>
|
|
167
|
+
<ul className="list-disc space-y-1 pl-5 text-xs text-muted-foreground leading-relaxed">
|
|
168
|
+
<li>The picture works on a plain-HTTP LAN address, not only over HTTPS or localhost.</li>
|
|
169
|
+
<li>A lost packet costs one retransmission instead of stalling the whole stream.</li>
|
|
170
|
+
<li>Keyboard, mouse, clipboard and host audio keep using the same WebSocket.</li>
|
|
171
|
+
<li>Video leaves this host on its own UDP port, protected by per-session credentials.</li>
|
|
172
|
+
</ul>
|
|
173
|
+
</section>
|
|
174
|
+
</div>
|
|
175
|
+
);
|
|
176
|
+
}
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
LayoutGrid,
|
|
24
24
|
Mic,
|
|
25
25
|
Zap,
|
|
26
|
+
Monitor,
|
|
26
27
|
} from "@/lib/icons";
|
|
27
28
|
|
|
28
29
|
/**
|
|
@@ -35,6 +36,7 @@ export type SettingsCategoryId =
|
|
|
35
36
|
| "general"
|
|
36
37
|
| "appearance"
|
|
37
38
|
| "language-servers"
|
|
39
|
+
| "remote-desktop"
|
|
38
40
|
| "ai-provider"
|
|
39
41
|
| "accounts"
|
|
40
42
|
| "design"
|
|
@@ -81,6 +83,7 @@ export const SETTINGS_CATEGORIES: SettingsCategoryDef[] = [
|
|
|
81
83
|
{ id: "general", group: "core", label: "General", subtitle: "Device name, password, version", icon: Settings2 },
|
|
82
84
|
{ id: "appearance", group: "core", label: "Appearance", subtitle: "Theme, tabs, explorer skin", icon: Palette },
|
|
83
85
|
{ id: "language-servers", group: "core", label: "Language Servers", subtitle: "Completions and go to definition, per language", icon: Zap },
|
|
86
|
+
{ id: "remote-desktop", group: "core", label: "Remote Desktop", subtitle: "WebRTC relay for streaming this host's screen", icon: Monitor },
|
|
84
87
|
|
|
85
88
|
{ id: "ai-provider", group: "ai", label: "AI Provider", subtitle: "Model, execution mode, limits", icon: Bot },
|
|
86
89
|
{ id: "accounts", group: "ai", label: "Accounts", subtitle: "Claude and Codex sign-ins, rotation", icon: KeyRound },
|
|
@@ -7,29 +7,36 @@
|
|
|
7
7
|
* does not pull every pane's dependencies (charts, editors, cron pickers) into the bundle.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { Suspense
|
|
10
|
+
import { Suspense } from "react";
|
|
11
|
+
import { lazyWithPreload, type PreloadableComponent } from "@/lib/lazy-with-preload";
|
|
11
12
|
import { Loader2 } from "@/lib/icons";
|
|
12
13
|
import type { SettingsCategoryId } from "./settings-categories";
|
|
13
14
|
|
|
14
|
-
const SECTIONS: Record<SettingsCategoryId,
|
|
15
|
-
general:
|
|
16
|
-
appearance:
|
|
17
|
-
"language-servers":
|
|
18
|
-
"
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
15
|
+
export const SECTIONS: Record<SettingsCategoryId, PreloadableComponent<object>> = {
|
|
16
|
+
general: lazyWithPreload(() => import("./general-settings-section").then((m) => ({ default: m.GeneralSettingsSection }))),
|
|
17
|
+
appearance: lazyWithPreload(() => import("./appearance-settings-section").then((m) => ({ default: m.AppearanceSettingsSection }))),
|
|
18
|
+
"language-servers": lazyWithPreload(() => import("./language-servers-section").then((m) => ({ default: m.LanguageServersSection }))),
|
|
19
|
+
"remote-desktop": lazyWithPreload(() => import("./remote-desktop-settings-section").then((m) => ({ default: m.RemoteDesktopSettingsSection }))),
|
|
20
|
+
"ai-provider": lazyWithPreload(() => import("./ai-settings-section").then((m) => ({ default: m.AISettingsSection }))),
|
|
21
|
+
accounts: lazyWithPreload(() => import("./accounts/accounts-settings-section").then((m) => ({ default: m.AccountsSettingsSection }))),
|
|
22
|
+
design: lazyWithPreload(() => import("./design-settings-section").then((m) => ({ default: m.DesignSettingsSection }))),
|
|
23
|
+
voice: lazyWithPreload(() => import("./voice-settings-section").then((m) => ({ default: m.VoiceSettingsSection }))),
|
|
24
|
+
ppmbot: lazyWithPreload(() => import("./ppmbot-settings-section").then((m) => ({ default: m.PPMBotSettingsSection }))),
|
|
25
|
+
notifications: lazyWithPreload(() => import("./notifications-settings-section").then((m) => ({ default: m.NotificationsSettingsSection }))),
|
|
26
|
+
jira: lazyWithPreload(() => import("./jira-watcher-section").then((m) => ({ default: m.JiraWatcherSection }))),
|
|
27
|
+
extensions: lazyWithPreload(() => import("./extension-manager-section").then((m) => ({ default: m.ExtensionManagerSection }))),
|
|
28
|
+
proxy: lazyWithPreload(() => import("./proxy-settings-section").then((m) => ({ default: m.ProxySettingsSection }))),
|
|
29
|
+
schedules: lazyWithPreload(() => import("./schedules/schedules-settings-section").then((m) => ({ default: m.SchedulesSettingsSection }))),
|
|
30
|
+
shortcuts: lazyWithPreload(() => import("./keyboard-shortcuts-section").then((m) => ({ default: m.KeyboardShortcutsSection }))),
|
|
31
|
+
files: lazyWithPreload(() => import("./files-settings-section").then((m) => ({ default: m.FilesSettingsSection }))),
|
|
32
|
+
"query-audit": lazyWithPreload(() => import("./query-audit-section").then((m) => ({ default: m.QueryAuditSection }))),
|
|
31
33
|
};
|
|
32
34
|
|
|
35
|
+
/** Load every pane's code, so that opening Settings shows its pane without a spinner. */
|
|
36
|
+
export function preloadSettingsSections(): Promise<unknown> {
|
|
37
|
+
return Promise.all(Object.values(SECTIONS).map((section) => section.preload()));
|
|
38
|
+
}
|
|
39
|
+
|
|
33
40
|
export function SettingsSectionContent({ category }: { category: SettingsCategoryId }) {
|
|
34
41
|
const Section = SECTIONS[category];
|
|
35
42
|
return (
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef } from "react";
|
|
2
|
+
import { api, projectUrl } from "@/lib/api-client";
|
|
3
|
+
import type { TurnSettings } from "@/hooks/use-chat";
|
|
4
|
+
|
|
5
|
+
/** How long the picks must hold still before asking, so a tab passed through asks nothing. */
|
|
6
|
+
const SETTLE_MS = 300;
|
|
7
|
+
/** Asked again at most this often while the user types; the server keeps a process 5 min. */
|
|
8
|
+
const REASK_MS = 60_000;
|
|
9
|
+
|
|
10
|
+
export interface ChatPrewarmInput {
|
|
11
|
+
/** A new chat is open and on screen: no session yet, not a design tab. */
|
|
12
|
+
enabled: boolean;
|
|
13
|
+
projectName: string;
|
|
14
|
+
providerId: string;
|
|
15
|
+
permissionMode?: string;
|
|
16
|
+
/** The account the tab claimed, which the session will be created on. */
|
|
17
|
+
accountId?: string;
|
|
18
|
+
/** What the first message will carry — `useChat().turnSettings()`. */
|
|
19
|
+
picks: TurnSettings;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Has the server start the Claude process a new chat's first message will run on while
|
|
24
|
+
* that message is still being written, so sending it does not wait ~1.4 s for the CLI to
|
|
25
|
+
* boot (`src/providers/claude-warm-spare.ts` has the numbers).
|
|
26
|
+
*
|
|
27
|
+
* Asks when the composer opens and whenever a pick changes — the process only serves a
|
|
28
|
+
* message that carries the picks it was started with. Returns `touch`, for the composer to
|
|
29
|
+
* call as the user types: it asks again at most once a minute, which keeps the process
|
|
30
|
+
* alive while someone is writing and lets it go when they walk away.
|
|
31
|
+
*/
|
|
32
|
+
export function useChatPrewarm(input: ChatPrewarmInput): () => void {
|
|
33
|
+
const { enabled, projectName, providerId, permissionMode, accountId, picks } = input;
|
|
34
|
+
const lastAskAt = useRef(0);
|
|
35
|
+
const ask = useRef(() => {});
|
|
36
|
+
const enabledRef = useRef(enabled);
|
|
37
|
+
enabledRef.current = enabled;
|
|
38
|
+
ask.current = () => {
|
|
39
|
+
lastAskAt.current = Date.now();
|
|
40
|
+
void api.post(`${projectUrl(projectName)}/chat/prewarm`, { providerId, permissionMode, accountId, ...picks }).catch(() => {});
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const key = JSON.stringify([projectName, providerId, permissionMode ?? null, accountId ?? null, picks]);
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
if (!enabled) return;
|
|
46
|
+
const timer = setTimeout(() => ask.current(), SETTLE_MS);
|
|
47
|
+
return () => clearTimeout(timer);
|
|
48
|
+
}, [enabled, key]);
|
|
49
|
+
|
|
50
|
+
return useCallback(() => {
|
|
51
|
+
if (enabledRef.current && Date.now() - lastAskAt.current >= REASK_MS) ask.current();
|
|
52
|
+
}, []);
|
|
53
|
+
}
|
|
@@ -54,6 +54,13 @@ export interface BashPartialEntry {
|
|
|
54
54
|
lineCount: number;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
/** The per-session picks a message carries; a pick the user never made is left out. */
|
|
58
|
+
export interface TurnSettings {
|
|
59
|
+
model?: string;
|
|
60
|
+
effort?: string;
|
|
61
|
+
thinking?: boolean;
|
|
62
|
+
}
|
|
63
|
+
|
|
57
64
|
interface UseChatReturn {
|
|
58
65
|
messages: ChatMessage[];
|
|
59
66
|
/** Messages flattened with pre-compact expansions prepended before their compact cards. */
|
|
@@ -102,6 +109,8 @@ interface UseChatReturn {
|
|
|
102
109
|
thinking: boolean;
|
|
103
110
|
/** Toggle per-session thinking on/off */
|
|
104
111
|
setThinking: (enabled: boolean) => void;
|
|
112
|
+
/** The picks the next message carries, exactly as `sendMessage` attaches them. */
|
|
113
|
+
turnSettings: () => TurnSettings;
|
|
105
114
|
/** Team activity state from WS events */
|
|
106
115
|
teamActivity: TeamActivityState;
|
|
107
116
|
/** All team messages (ref-backed, updated live) */
|
|
@@ -1296,6 +1305,12 @@ export function useChat(
|
|
|
1296
1305
|
};
|
|
1297
1306
|
}, [sessionId, providerId, projectName, updateTeamActivity, loadTeamDetail]);
|
|
1298
1307
|
|
|
1308
|
+
const turnSettings = useCallback((): TurnSettings => ({
|
|
1309
|
+
...(modelRef.current && { model: modelRef.current }),
|
|
1310
|
+
...(effortRef.current && { effort: effortRef.current }),
|
|
1311
|
+
...(thinkingRef.current !== null && { thinking: thinkingRef.current }),
|
|
1312
|
+
}), []);
|
|
1313
|
+
|
|
1299
1314
|
const sendMessage = useCallback(
|
|
1300
1315
|
(content: string, opts?: { permissionMode?: string; priority?: 'now' | 'next' | 'later'; images?: Array<{ data: string; mediaType: string }>; imagePaths?: string[] }) => {
|
|
1301
1316
|
// An attachment-only message is legitimate now that images travel with it: the
|
|
@@ -1357,12 +1372,10 @@ export function useChat(
|
|
|
1357
1372
|
priority: opts?.priority,
|
|
1358
1373
|
images: opts?.images,
|
|
1359
1374
|
imagePaths: opts?.imagePaths,
|
|
1360
|
-
...(
|
|
1361
|
-
...(effortRef.current && { effort: effortRef.current }),
|
|
1362
|
-
...(thinkingRef.current !== null && { thinking: thinkingRef.current }),
|
|
1375
|
+
...turnSettings(),
|
|
1363
1376
|
}));
|
|
1364
1377
|
},
|
|
1365
|
-
[send, isConnected, connectedSessionId],
|
|
1378
|
+
[send, isConnected, connectedSessionId, turnSettings],
|
|
1366
1379
|
);
|
|
1367
1380
|
|
|
1368
1381
|
const setModel = useCallback(
|
|
@@ -1602,6 +1615,7 @@ export function useChat(
|
|
|
1602
1615
|
setEffort,
|
|
1603
1616
|
thinking,
|
|
1604
1617
|
setThinking,
|
|
1618
|
+
turnSettings,
|
|
1605
1619
|
teamActivity,
|
|
1606
1620
|
teamMessages,
|
|
1607
1621
|
markTeamRead,
|