@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,277 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The viewer's half of `/ws/android`: mint a nonce, connect, decode, keep the lease alive.
|
|
3
|
+
*
|
|
4
|
+
* Four things here are load-bearing and each one looks optional until it is missing:
|
|
5
|
+
*
|
|
6
|
+
* - **The nonce is minted per connection attempt, and it is single use.** A reconnect mints a
|
|
7
|
+
* new one; reusing the old one is rejected by design, so the retry path must go through the
|
|
8
|
+
* route again rather than caching it.
|
|
9
|
+
* - **The heartbeat is what holds the controller lease.** Stop sending for 15 seconds and the
|
|
10
|
+
* server hands control to someone else — which is the intended behaviour for a closed laptop
|
|
11
|
+
* lid, and a bug if the timer is tied to a render.
|
|
12
|
+
* - **The decoder is reconfigured from the server's `codec` message, never from a guess.** The
|
|
13
|
+
* encoder sets no profile or level, so the string is encoder-default and only knowable from
|
|
14
|
+
* the real SPS. A rung switch and a rotation both produce a new one.
|
|
15
|
+
* - **Deltas before a keyframe are dropped.** A `VideoDecoder` cannot start on one, and neither
|
|
16
|
+
* can a freshly recreated one after an error — `shouldDecodeAccessUnit` is the shared rule.
|
|
17
|
+
*/
|
|
18
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
19
|
+
import { api } from "@/lib/api-client";
|
|
20
|
+
import { useH264CanvasDecoder, shouldDecodeAccessUnit } from "@/components/remote-desktop/use-h264-canvas-decoder";
|
|
21
|
+
import { resolveAndroidWsUrl } from "./android-ws-url";
|
|
22
|
+
import {
|
|
23
|
+
ANDROID_PROTOCOL_VERSION, decodeVideoFrame,
|
|
24
|
+
type AndroidClientMessage, type AndroidGeometry, type AndroidLogEntry, type AndroidQuality,
|
|
25
|
+
type AndroidServerMessage,
|
|
26
|
+
} from "../../../shared/android-protocol";
|
|
27
|
+
|
|
28
|
+
const HEARTBEAT_MS = 5_000;
|
|
29
|
+
/** Long enough that a phone switching cell tower does not lose the picture, short enough that a
|
|
30
|
+
* dead socket is noticed. Backed off so a server that is down is not hammered. */
|
|
31
|
+
const RECONNECT_DELAYS_MS = [500, 1_000, 2_000, 4_000, 8_000];
|
|
32
|
+
|
|
33
|
+
export type AndroidConnectionState = "connecting" | "live" | "reconnecting" | "failed";
|
|
34
|
+
|
|
35
|
+
export interface UseAndroidSessionOptions {
|
|
36
|
+
deviceId: string;
|
|
37
|
+
quality: AndroidQuality;
|
|
38
|
+
canvasRef: React.RefObject<HTMLCanvasElement | null>;
|
|
39
|
+
/** Paused sessions stop receiving frames; the emulator and the lease stay up. */
|
|
40
|
+
paused?: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface AndroidSession {
|
|
44
|
+
state: AndroidConnectionState;
|
|
45
|
+
errorMessage: string | null;
|
|
46
|
+
geometry: AndroidGeometry | null;
|
|
47
|
+
controller: boolean;
|
|
48
|
+
controllerReason: string | null;
|
|
49
|
+
encoder: string | null;
|
|
50
|
+
/** Null until the first keyframe has been decoded — the viewer shows a spinner until then. */
|
|
51
|
+
hasPicture: boolean;
|
|
52
|
+
send: (message: AndroidClientMessage) => void;
|
|
53
|
+
takeControl: () => void;
|
|
54
|
+
reconnect: () => void;
|
|
55
|
+
/**
|
|
56
|
+
* Turn the device's log feed on or off.
|
|
57
|
+
*
|
|
58
|
+
* The server stops the gRPC stream when the last watcher leaves (plan gate: "logs hidden
|
|
59
|
+
* ngừng subscription"), so this is not a client-side filter — asking for it off really does
|
|
60
|
+
* stop the work on the host.
|
|
61
|
+
*/
|
|
62
|
+
setLogcat: (on: boolean) => void;
|
|
63
|
+
/**
|
|
64
|
+
* Listen for log batches. Deliberately a subscription rather than state: a chatty device
|
|
65
|
+
* produces hundreds of entries a second, and putting them in this hook's state would
|
|
66
|
+
* re-render the canvas's parent at that rate.
|
|
67
|
+
*/
|
|
68
|
+
onLog: (listener: (entries: AndroidLogEntry[]) => void) => () => void;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function useAndroidSession(opts: UseAndroidSessionOptions): AndroidSession {
|
|
72
|
+
const { deviceId, quality, canvasRef, paused = false } = opts;
|
|
73
|
+
|
|
74
|
+
const [state, setState] = useState<AndroidConnectionState>("connecting");
|
|
75
|
+
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
|
76
|
+
const [geometry, setGeometry] = useState<AndroidGeometry | null>(null);
|
|
77
|
+
const [controller, setController] = useState(false);
|
|
78
|
+
const [controllerReason, setControllerReason] = useState<string | null>(null);
|
|
79
|
+
const [encoder, setEncoder] = useState<string | null>(null);
|
|
80
|
+
const [hasPicture, setHasPicture] = useState(false);
|
|
81
|
+
|
|
82
|
+
const decoder = useH264CanvasDecoder(canvasRef);
|
|
83
|
+
const socketRef = useRef<WebSocket | null>(null);
|
|
84
|
+
const attemptRef = useRef(0);
|
|
85
|
+
// Bumped by every connection attempt, and only the newest may act. The socket a manual
|
|
86
|
+
// reconnect replaced still fires `onclose` after its handshake, and a nonce request can still
|
|
87
|
+
// be out when the viewer closes: left to run, either one would clear the live socket's ref,
|
|
88
|
+
// schedule a retry nobody asked for, or open a socket nothing will ever close.
|
|
89
|
+
const generationRef = useRef(0);
|
|
90
|
+
const reconnectTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
91
|
+
const configuredCodecRef = useRef<string | null>(null);
|
|
92
|
+
// Read inside the socket's handlers, which are created once per connection — a state read
|
|
93
|
+
// there would be the value from the render that created them.
|
|
94
|
+
const qualityRef = useRef(quality);
|
|
95
|
+
qualityRef.current = quality;
|
|
96
|
+
const hasPictureRef = useRef(false);
|
|
97
|
+
const logListenersRef = useRef(new Set<(entries: AndroidLogEntry[]) => void>());
|
|
98
|
+
// Survives a reconnect: the subscription is per socket on the server, so a dropped connection
|
|
99
|
+
// silently ends it and the panel would show a log that stopped without saying why.
|
|
100
|
+
const logcatWantedRef = useRef(false);
|
|
101
|
+
|
|
102
|
+
const send = useCallback((message: AndroidClientMessage) => {
|
|
103
|
+
const socket = socketRef.current;
|
|
104
|
+
if (socket?.readyState === WebSocket.OPEN) socket.send(JSON.stringify(message));
|
|
105
|
+
}, []);
|
|
106
|
+
|
|
107
|
+
const takeControl = useCallback(() => send({ type: "take-control" }), [send]);
|
|
108
|
+
|
|
109
|
+
const handleServerMessage = useCallback((message: AndroidServerMessage) => {
|
|
110
|
+
switch (message.type) {
|
|
111
|
+
case "ready":
|
|
112
|
+
attemptRef.current = 0;
|
|
113
|
+
setState("live");
|
|
114
|
+
setErrorMessage(null);
|
|
115
|
+
setGeometry(message.geometry);
|
|
116
|
+
setController(message.controller);
|
|
117
|
+
setControllerReason(null);
|
|
118
|
+
setEncoder(message.encoder);
|
|
119
|
+
if (message.codec) {
|
|
120
|
+
configuredCodecRef.current = message.codec;
|
|
121
|
+
void decoder.configure(message.codec);
|
|
122
|
+
}
|
|
123
|
+
if (logcatWantedRef.current) send({ type: "logcat", subscribe: true });
|
|
124
|
+
return;
|
|
125
|
+
case "log":
|
|
126
|
+
for (const listener of logListenersRef.current) listener(message.entries);
|
|
127
|
+
return;
|
|
128
|
+
case "codec":
|
|
129
|
+
if (message.codec === configuredCodecRef.current) return;
|
|
130
|
+
configuredCodecRef.current = message.codec;
|
|
131
|
+
void decoder.configure(message.codec);
|
|
132
|
+
return;
|
|
133
|
+
case "geometry":
|
|
134
|
+
setGeometry(message.geometry);
|
|
135
|
+
// The decoder is holding parameters for a size that no longer exists; the server's
|
|
136
|
+
// `codec` message for the new bitstream is what reconfigures it.
|
|
137
|
+
decoder.reset();
|
|
138
|
+
setHasPicture(false);
|
|
139
|
+
return;
|
|
140
|
+
case "controller":
|
|
141
|
+
setController(message.controller);
|
|
142
|
+
setControllerReason(message.reason ?? null);
|
|
143
|
+
return;
|
|
144
|
+
case "error":
|
|
145
|
+
setErrorMessage(message.message);
|
|
146
|
+
return;
|
|
147
|
+
default:
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
}, [decoder, send]);
|
|
151
|
+
|
|
152
|
+
/** Silence the current attempt: its callbacks stop acting, its socket and retry are dropped. */
|
|
153
|
+
const retire = useCallback(() => {
|
|
154
|
+
generationRef.current += 1;
|
|
155
|
+
if (reconnectTimerRef.current) clearTimeout(reconnectTimerRef.current);
|
|
156
|
+
reconnectTimerRef.current = null;
|
|
157
|
+
socketRef.current?.close();
|
|
158
|
+
socketRef.current = null;
|
|
159
|
+
}, []);
|
|
160
|
+
|
|
161
|
+
const connect = useCallback(async () => {
|
|
162
|
+
retire();
|
|
163
|
+
const generation = generationRef.current;
|
|
164
|
+
const current = () => generationRef.current === generation;
|
|
165
|
+
setErrorMessage(null);
|
|
166
|
+
|
|
167
|
+
let nonce: string;
|
|
168
|
+
try {
|
|
169
|
+
// Single use and short-lived: a reconnect must mint a fresh one, never replay this.
|
|
170
|
+
const res = await api.post<{ nonce: string }>(`/api/android/devices/${encodeURIComponent(deviceId)}/sessions`, {});
|
|
171
|
+
nonce = res.nonce;
|
|
172
|
+
} catch (e) {
|
|
173
|
+
if (!current()) return;
|
|
174
|
+
setErrorMessage((e as Error).message);
|
|
175
|
+
setState("failed");
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
if (!current()) return;
|
|
179
|
+
|
|
180
|
+
const socket = new WebSocket(resolveAndroidWsUrl(window.location, import.meta.env.DEV));
|
|
181
|
+
socket.binaryType = "arraybuffer";
|
|
182
|
+
socketRef.current = socket;
|
|
183
|
+
|
|
184
|
+
socket.onopen = () => {
|
|
185
|
+
socket.send(JSON.stringify({ type: "auth", nonce, quality: qualityRef.current } satisfies AndroidClientMessage));
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
socket.onmessage = (event) => {
|
|
189
|
+
// A replaced socket can still deliver what was queued before its close: its `ready` would
|
|
190
|
+
// mark the viewer live, and its frames would feed the decoder, while the new one connects.
|
|
191
|
+
if (!current()) return;
|
|
192
|
+
if (typeof event.data === "string") {
|
|
193
|
+
try { handleServerMessage(JSON.parse(event.data) as AndroidServerMessage); } catch { /* not ours */ }
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
const frame = decodeVideoFrame(event.data as ArrayBuffer);
|
|
197
|
+
// A frame that fails to decode here was truncated or coalesced in transit; feeding its
|
|
198
|
+
// bytes to the decoder anyway is how a stream turns to garbage rather than erroring.
|
|
199
|
+
if (!frame || frame.header.version !== ANDROID_PROTOCOL_VERSION) return;
|
|
200
|
+
if (!shouldDecodeAccessUnit(hasPictureRef.current, frame.header.keyframe)) return;
|
|
201
|
+
hasPictureRef.current = true;
|
|
202
|
+
setHasPicture(true);
|
|
203
|
+
decoder.decodeAccessUnit(frame.payload, frame.header.keyframe);
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
socket.onclose = () => {
|
|
207
|
+
if (!current()) return;
|
|
208
|
+
socketRef.current = null;
|
|
209
|
+
const delay = RECONNECT_DELAYS_MS[Math.min(attemptRef.current, RECONNECT_DELAYS_MS.length - 1)]!;
|
|
210
|
+
attemptRef.current += 1;
|
|
211
|
+
setState(attemptRef.current > RECONNECT_DELAYS_MS.length ? "failed" : "reconnecting");
|
|
212
|
+
hasPictureRef.current = false;
|
|
213
|
+
setHasPicture(false);
|
|
214
|
+
decoder.reset();
|
|
215
|
+
configuredCodecRef.current = null;
|
|
216
|
+
reconnectTimerRef.current = setTimeout(() => { void connect(); }, delay);
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
socket.onerror = () => { /* onclose does the work; this only stops an unhandled event */ };
|
|
220
|
+
}, [deviceId, decoder, handleServerMessage, retire]);
|
|
221
|
+
|
|
222
|
+
useEffect(() => {
|
|
223
|
+
void connect();
|
|
224
|
+
return () => {
|
|
225
|
+
retire();
|
|
226
|
+
decoder.reset();
|
|
227
|
+
};
|
|
228
|
+
// `connect` is stable per deviceId; re-running on every decoder identity change would
|
|
229
|
+
// reconnect the socket for no reason.
|
|
230
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
231
|
+
}, [deviceId]);
|
|
232
|
+
|
|
233
|
+
// The heartbeat is the lease. A tab that stops sending loses control after 15s but keeps
|
|
234
|
+
// watching, so this runs regardless of whether this client currently holds it.
|
|
235
|
+
useEffect(() => {
|
|
236
|
+
const timer = setInterval(() => send({ type: "heartbeat" }), HEARTBEAT_MS);
|
|
237
|
+
return () => clearInterval(timer);
|
|
238
|
+
}, [send]);
|
|
239
|
+
|
|
240
|
+
useEffect(() => { send({ type: "visibility", visible: !paused }); }, [paused, send]);
|
|
241
|
+
|
|
242
|
+
useEffect(() => {
|
|
243
|
+
if (state === "live") send({ type: "quality", quality });
|
|
244
|
+
}, [quality, state, send]);
|
|
245
|
+
|
|
246
|
+
const setLogcat = useCallback((on: boolean) => {
|
|
247
|
+
logcatWantedRef.current = on;
|
|
248
|
+
send({ type: "logcat", subscribe: on });
|
|
249
|
+
}, [send]);
|
|
250
|
+
|
|
251
|
+
const onLog = useCallback((listener: (entries: AndroidLogEntry[]) => void) => {
|
|
252
|
+
const set = logListenersRef.current;
|
|
253
|
+
set.add(listener);
|
|
254
|
+
return () => { set.delete(listener); };
|
|
255
|
+
}, []);
|
|
256
|
+
|
|
257
|
+
const reconnect = useCallback(() => {
|
|
258
|
+
attemptRef.current = 0;
|
|
259
|
+
setState("connecting");
|
|
260
|
+
void connect();
|
|
261
|
+
}, [connect]);
|
|
262
|
+
|
|
263
|
+
return {
|
|
264
|
+
state,
|
|
265
|
+
errorMessage: errorMessage ?? decoder.errorMessage,
|
|
266
|
+
geometry,
|
|
267
|
+
controller,
|
|
268
|
+
controllerReason,
|
|
269
|
+
encoder,
|
|
270
|
+
hasPicture,
|
|
271
|
+
send,
|
|
272
|
+
takeControl,
|
|
273
|
+
reconnect,
|
|
274
|
+
setLogcat,
|
|
275
|
+
onLog,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pointer events on the canvas -> `sendTouch` messages.
|
|
3
|
+
*
|
|
4
|
+
* Five things that are not optional:
|
|
5
|
+
*
|
|
6
|
+
* - **`touch-action: none`** on the canvas, or the browser eats the gesture as a scroll or a
|
|
7
|
+
* pinch and the guest sees a fraction of it. Set in the viewer's class list, not here.
|
|
8
|
+
* - **Pointer capture**, so a drag that leaves the canvas keeps reporting. Without it a swipe
|
|
9
|
+
* off the edge simply stops, and the finger is never released.
|
|
10
|
+
* - **Every active contact is sent on every update.** The emulator tracks slots by identifier
|
|
11
|
+
* and a message describing only the finger that moved would be read as the others lifting.
|
|
12
|
+
* - **A release is pressure 0, and it must be sent.** The proto is explicit: "Make sure to
|
|
13
|
+
* deliver a pressure of 0 for the given identifier when the touch event is completed,
|
|
14
|
+
* otherwise the touch identifier will not be unregistered". The 120-second expiry is not a
|
|
15
|
+
* substitute.
|
|
16
|
+
* - **`pointercancel` is handled exactly like `pointerup`.** The browser fires it instead of
|
|
17
|
+
* `pointerup` once it decides a gesture belongs to something else, and then delivers nothing
|
|
18
|
+
* further for that pointer — the same class of bug as the long-press trap in CLAUDE.md, only
|
|
19
|
+
* here it leaves a finger pressed on the guest forever.
|
|
20
|
+
*/
|
|
21
|
+
import { useCallback, useEffect, useRef } from "react";
|
|
22
|
+
import { pointerToDevice } from "./android-coords";
|
|
23
|
+
import type { AndroidClientMessage, AndroidGeometry, AndroidTouchPoint } from "../../../shared/android-protocol";
|
|
24
|
+
|
|
25
|
+
/** What the emulator reports for a finger that is down. Any non-zero value means "touching". */
|
|
26
|
+
const PRESSURE_DOWN = 1;
|
|
27
|
+
const MAX_SLOTS = 10;
|
|
28
|
+
|
|
29
|
+
export interface UseAndroidTouchOptions {
|
|
30
|
+
canvasRef: React.RefObject<HTMLCanvasElement | null>;
|
|
31
|
+
geometry: AndroidGeometry | null;
|
|
32
|
+
enabled: boolean;
|
|
33
|
+
send: (message: AndroidClientMessage) => void;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface AndroidTouchHandlers {
|
|
37
|
+
onPointerDown: (e: React.PointerEvent<HTMLCanvasElement>) => void;
|
|
38
|
+
onPointerMove: (e: React.PointerEvent<HTMLCanvasElement>) => void;
|
|
39
|
+
onPointerUp: (e: React.PointerEvent<HTMLCanvasElement>) => void;
|
|
40
|
+
onPointerCancel: (e: React.PointerEvent<HTMLCanvasElement>) => void;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function useAndroidTouch(opts: UseAndroidTouchOptions): AndroidTouchHandlers {
|
|
44
|
+
const { canvasRef, geometry, enabled, send } = opts;
|
|
45
|
+
|
|
46
|
+
/** Browser pointerId -> the emulator slot it owns, plus its last position. */
|
|
47
|
+
const contacts = useRef(new Map<number, { slot: number; x: number; y: number }>());
|
|
48
|
+
|
|
49
|
+
const geometryRef = useRef(geometry);
|
|
50
|
+
geometryRef.current = geometry;
|
|
51
|
+
const enabledRef = useRef(enabled);
|
|
52
|
+
enabledRef.current = enabled;
|
|
53
|
+
|
|
54
|
+
const publish = useCallback((releasing?: { slot: number; x: number; y: number }) => {
|
|
55
|
+
const g = geometryRef.current;
|
|
56
|
+
if (!g) return;
|
|
57
|
+
const touches: AndroidTouchPoint[] = [...contacts.current.values()].map((c) => ({
|
|
58
|
+
x: c.x, y: c.y, id: c.slot, pressure: PRESSURE_DOWN,
|
|
59
|
+
}));
|
|
60
|
+
if (releasing) touches.push({ x: releasing.x, y: releasing.y, id: releasing.slot, pressure: 0 });
|
|
61
|
+
if (touches.length === 0) return;
|
|
62
|
+
send({ type: "touch", geometryGeneration: g.generation, touches });
|
|
63
|
+
}, [send]);
|
|
64
|
+
|
|
65
|
+
const locate = useCallback((e: React.PointerEvent<HTMLCanvasElement>) => {
|
|
66
|
+
const canvas = canvasRef.current;
|
|
67
|
+
const g = geometryRef.current;
|
|
68
|
+
if (!canvas || !g) return null;
|
|
69
|
+
const rect = canvas.getBoundingClientRect();
|
|
70
|
+
return pointerToDevice(rect, g, e.clientX - rect.left, e.clientY - rect.top);
|
|
71
|
+
}, [canvasRef]);
|
|
72
|
+
|
|
73
|
+
const onPointerDown = useCallback((e: React.PointerEvent<HTMLCanvasElement>) => {
|
|
74
|
+
if (!enabledRef.current) return;
|
|
75
|
+
const point = locate(e);
|
|
76
|
+
if (!point) return; // a letterbox bar belongs to no pixel of the guest
|
|
77
|
+
if (contacts.current.size >= MAX_SLOTS) return;
|
|
78
|
+
const used = new Set([...contacts.current.values()].map((c) => c.slot));
|
|
79
|
+
let slot = 0;
|
|
80
|
+
while (used.has(slot)) slot++;
|
|
81
|
+
contacts.current.set(e.pointerId, { slot, x: point.x, y: point.y });
|
|
82
|
+
// Capture, so a drag that leaves the canvas keeps reporting and can still be released.
|
|
83
|
+
try { e.currentTarget.setPointerCapture(e.pointerId); } catch { /* not capturable */ }
|
|
84
|
+
publish();
|
|
85
|
+
}, [locate, publish]);
|
|
86
|
+
|
|
87
|
+
const onPointerMove = useCallback((e: React.PointerEvent<HTMLCanvasElement>) => {
|
|
88
|
+
const contact = contacts.current.get(e.pointerId);
|
|
89
|
+
if (!contact || !enabledRef.current) return;
|
|
90
|
+
const point = locate(e);
|
|
91
|
+
// Outside the picture the finger keeps its last position rather than jumping or lifting: a
|
|
92
|
+
// swipe that overshoots the edge is still one continuous gesture to the guest.
|
|
93
|
+
if (point) { contact.x = point.x; contact.y = point.y; }
|
|
94
|
+
publish();
|
|
95
|
+
}, [locate, publish]);
|
|
96
|
+
|
|
97
|
+
const release = useCallback((e: React.PointerEvent<HTMLCanvasElement>) => {
|
|
98
|
+
const contact = contacts.current.get(e.pointerId);
|
|
99
|
+
if (!contact) return;
|
|
100
|
+
contacts.current.delete(e.pointerId);
|
|
101
|
+
try { e.currentTarget.releasePointerCapture(e.pointerId); } catch { /* already released */ }
|
|
102
|
+
publish(contact);
|
|
103
|
+
}, [publish]);
|
|
104
|
+
|
|
105
|
+
// A tab hidden or a window blurred mid-drag delivers no further pointer events at all, so the
|
|
106
|
+
// only way the guest ever sees those fingers lift is for the server to clear every slot.
|
|
107
|
+
useEffect(() => {
|
|
108
|
+
const reset = () => {
|
|
109
|
+
if (contacts.current.size === 0) return;
|
|
110
|
+
contacts.current.clear();
|
|
111
|
+
send({ type: "input-reset" });
|
|
112
|
+
};
|
|
113
|
+
window.addEventListener("blur", reset);
|
|
114
|
+
document.addEventListener("visibilitychange", reset);
|
|
115
|
+
return () => {
|
|
116
|
+
window.removeEventListener("blur", reset);
|
|
117
|
+
document.removeEventListener("visibilitychange", reset);
|
|
118
|
+
reset();
|
|
119
|
+
};
|
|
120
|
+
}, [send]);
|
|
121
|
+
|
|
122
|
+
return { onPointerDown, onPointerMove, onPointerUp: release, onPointerCancel: release };
|
|
123
|
+
}
|
|
@@ -6,12 +6,15 @@ import { selectInlineImages } from "@/lib/image-resize-limits";
|
|
|
6
6
|
import { splitAttachmentMarkers } from "@/lib/attachment-marker-split";
|
|
7
7
|
import type { ChatAttemptEvent } from "@/lib/chat-attempt-lifecycle";
|
|
8
8
|
import { useChat } from "@/hooks/use-chat";
|
|
9
|
+
import { useChatPrewarm } from "@/hooks/use-chat-prewarm";
|
|
9
10
|
import { useUsage } from "@/hooks/use-usage";
|
|
10
11
|
import { useDesignSessionRedirect } from "@/hooks/use-design-session-redirect";
|
|
11
12
|
import { useTabStore } from "@/stores/tab-store";
|
|
12
13
|
import { useSettingsStore } from "@/stores/settings-store";
|
|
13
14
|
import { usePanelStore } from "@/stores/panel-store";
|
|
14
15
|
import { useNotificationStore } from "@/stores/notification-store";
|
|
16
|
+
import { useFileStore } from "@/stores/file-store";
|
|
17
|
+
import { useRemoteFileSearch } from "@/hooks/use-remote-file-search";
|
|
15
18
|
import { openBugReportPopup } from "@/lib/report-bug";
|
|
16
19
|
import { useChatAccountClaim } from "@/hooks/use-chat-account-claim";
|
|
17
20
|
import { startPrepare, getPrepare, isPrepared, forgetPrepare } from "@/lib/new-chat-prepare-client";
|
|
@@ -399,6 +402,7 @@ function ChatTabContent({ metadata, tabId, onNewSession, onFork, historyFilter }
|
|
|
399
402
|
setEffort,
|
|
400
403
|
thinking,
|
|
401
404
|
setThinking,
|
|
405
|
+
turnSettings,
|
|
402
406
|
sendMessage,
|
|
403
407
|
respondToApproval,
|
|
404
408
|
cancelStreaming,
|
|
@@ -413,6 +417,19 @@ function ChatTabContent({ metadata, tabId, onNewSession, onFork, historyFilter }
|
|
|
413
417
|
killBackgroundShell,
|
|
414
418
|
} = useChat(sessionId, providerId, projectName, handleSessionMigrated, observeAttempt);
|
|
415
419
|
|
|
420
|
+
// `model`/`effort`/`thinking` are what change when a pick does; the picks sent are read
|
|
421
|
+
// through `turnSettings`, which leaves out whatever the user has not chosen.
|
|
422
|
+
const firstMessagePicks = useMemo(() => turnSettings(), [turnSettings, model, effort, thinking]);
|
|
423
|
+
const touchPrewarm = useChatPrewarm({
|
|
424
|
+
// Not while the provider is still being resolved: the process would start for a guess.
|
|
425
|
+
enabled: !sessionId && !designSlug && tourTabActive && !!projectName && !preparation.pending,
|
|
426
|
+
projectName,
|
|
427
|
+
providerId,
|
|
428
|
+
permissionMode,
|
|
429
|
+
accountId: claimMatchesProvider ? pickedAccountId : undefined,
|
|
430
|
+
picks: firstMessagePicks,
|
|
431
|
+
});
|
|
432
|
+
|
|
416
433
|
useEffect(() => {
|
|
417
434
|
if (!tabId || !tourTabActive || draftLoading || isStreaming) return;
|
|
418
435
|
const announce = () => window.dispatchEvent(new CustomEvent("ppm:onboarding-evidence", {
|
|
@@ -1006,8 +1023,9 @@ function ChatTabContent({ metadata, tabId, onNewSession, onFork, historyFilter }
|
|
|
1006
1023
|
const handleContentChange = useCallback(
|
|
1007
1024
|
(content: string, attachments?: DraftAttachment[]) => {
|
|
1008
1025
|
saveDraft(content, attachments);
|
|
1026
|
+
touchPrewarm();
|
|
1009
1027
|
},
|
|
1010
|
-
[saveDraft],
|
|
1028
|
+
[saveDraft, touchPrewarm],
|
|
1011
1029
|
);
|
|
1012
1030
|
|
|
1013
1031
|
/** Stable callback for slash items loaded — prevents MessageInput memo break */
|
|
@@ -1057,6 +1075,14 @@ function ChatTabContent({ metadata, tabId, onNewSession, onFork, historyFilter }
|
|
|
1057
1075
|
}, []);
|
|
1058
1076
|
|
|
1059
1077
|
// --- File picker handlers ---
|
|
1078
|
+
// An open @-picker keeps the file index current, like the palette — see `openIndexReader`.
|
|
1079
|
+
useEffect(() => {
|
|
1080
|
+
if (showFilePicker && projectName) return useFileStore.getState().openIndexReader(projectName);
|
|
1081
|
+
}, [showFilePicker, projectName]);
|
|
1082
|
+
// A project too long to send is searched on the server as the @-query changes.
|
|
1083
|
+
const indexRemote = useFileStore((s) => s.indexRemote);
|
|
1084
|
+
const remoteFileItems = useRemoteFileSearch(projectName, fileFilter, { enabled: showFilePicker && indexRemote, kind: "all" });
|
|
1085
|
+
|
|
1060
1086
|
const handleFileStateChange = useCallback((visible: boolean, filter: string) => {
|
|
1061
1087
|
setShowFilePicker(visible);
|
|
1062
1088
|
setFileFilter(filter);
|
|
@@ -1247,7 +1273,7 @@ function ChatTabContent({ metadata, tabId, onNewSession, onFork, historyFilter }
|
|
|
1247
1273
|
projectName={projectName}
|
|
1248
1274
|
/>
|
|
1249
1275
|
<FilePicker
|
|
1250
|
-
items={fileItems}
|
|
1276
|
+
items={indexRemote ? remoteFileItems : fileItems}
|
|
1251
1277
|
filter={fileFilter}
|
|
1252
1278
|
onSelect={handleFileSelect}
|
|
1253
1279
|
onClose={handleFileClose}
|
|
@@ -470,7 +470,7 @@ export const MessageInput = memo(function MessageInput({
|
|
|
470
470
|
void files.loadIndex(projectName);
|
|
471
471
|
return;
|
|
472
472
|
}
|
|
473
|
-
const nodes: FileNode[] = files.
|
|
473
|
+
const nodes: FileNode[] = files.indexProject === projectName ? files.fileIndex : [];
|
|
474
474
|
fileItemsRef.current = nodes;
|
|
475
475
|
onFileItemsLoaded?.(nodes);
|
|
476
476
|
};
|
|
@@ -826,10 +826,8 @@ export const MessageInput = memo(function MessageInput({
|
|
|
826
826
|
if (hasAt) {
|
|
827
827
|
const atMatch = textBefore.match(/@(\S*)$/);
|
|
828
828
|
if (atMatch) {
|
|
829
|
-
|
|
830
|
-
if (
|
|
831
|
-
void files.loadIndex(projectName);
|
|
832
|
-
}
|
|
829
|
+
// The index is refreshed only when something opens to read it — see `indexStale`.
|
|
830
|
+
if (!filePickerOpenRef.current && projectName) useFileStore.getState().ensureIndex(projectName);
|
|
833
831
|
onFileStateChange?.(true, atMatch[1] ?? "");
|
|
834
832
|
filePickerOpenRef.current = true;
|
|
835
833
|
if (slashPickerOpenRef.current) { onSlashStateChange?.(false, ""); slashPickerOpenRef.current = false; }
|
|
@@ -3,6 +3,7 @@ import { Columns2, FileCode, X } from "@/lib/icons";
|
|
|
3
3
|
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
|
4
4
|
import { useTabStore } from "@/stores/tab-store";
|
|
5
5
|
import { useFileStore } from "@/stores/file-store";
|
|
6
|
+
import { useRemoteFileSearch } from "@/hooks/use-remote-file-search";
|
|
6
7
|
import { useProjectStore } from "@/stores/project-store";
|
|
7
8
|
import { useCompareStore, type CompareSelection } from "@/stores/compare-store";
|
|
8
9
|
import { openCompareTab } from "@/lib/open-compare-tab";
|
|
@@ -64,6 +65,15 @@ export function ComparePicker({ open: openProp, onOpenChange, initialA }: Compar
|
|
|
64
65
|
const tabs = useTabStore((s) => s.tabs);
|
|
65
66
|
const fileIndex = useFileStore((s) => s.fileIndex);
|
|
66
67
|
const activeProject = useProjectStore((s) => s.activeProject);
|
|
68
|
+
// A project too long to send is searched on the server, and ranked below like the list itself.
|
|
69
|
+
const indexRemote = useFileStore((s) => s.indexRemote);
|
|
70
|
+
const remoteFiles = useRemoteFileSearch(activeProject?.name, query, { enabled: open && indexRemote });
|
|
71
|
+
|
|
72
|
+
// The index is refreshed only when something opens to read it — see `indexStale` — and kept
|
|
73
|
+
// current while that stays open.
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
if (open && activeProject) return useFileStore.getState().openIndexReader(activeProject.name);
|
|
76
|
+
}, [open, activeProject]);
|
|
67
77
|
|
|
68
78
|
useEffect(() => {
|
|
69
79
|
if (!open) return;
|
|
@@ -88,7 +98,7 @@ export function ComparePicker({ open: openProp, onOpenChange, initialA }: Compar
|
|
|
88
98
|
dirtyContent: t.metadata!.unsavedContent as string | undefined,
|
|
89
99
|
}));
|
|
90
100
|
const seenPaths = new Set(tabCands.map((c) => c.path));
|
|
91
|
-
const fileCands: Candidate[] = fileIndex
|
|
101
|
+
const fileCands: Candidate[] = (indexRemote ? remoteFiles : fileIndex)
|
|
92
102
|
.filter((f) => f.type === "file" && !seenPaths.has(f.path))
|
|
93
103
|
.map((f) => ({
|
|
94
104
|
id: `file:${f.path}`,
|
|
@@ -97,7 +107,7 @@ export function ComparePicker({ open: openProp, onOpenChange, initialA }: Compar
|
|
|
97
107
|
source: "file",
|
|
98
108
|
}));
|
|
99
109
|
return [...tabCands, ...fileCands];
|
|
100
|
-
}, [tabs, fileIndex]);
|
|
110
|
+
}, [tabs, fileIndex, indexRemote, remoteFiles]);
|
|
101
111
|
|
|
102
112
|
const filtered = useMemo<Candidate[]>(() => {
|
|
103
113
|
if (!query.trim()) return candidates.slice(0, MAX_RESULTS);
|
|
@@ -358,8 +358,9 @@ export function FileTree({ onFileOpen }: FileTreeProps = {}) {
|
|
|
358
358
|
savePersistedExpanded(activeProject.name, useFileStore.getState().expandedPaths);
|
|
359
359
|
}, [expandedPaths, activeProject?.name]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
360
360
|
|
|
361
|
-
//
|
|
362
|
-
//
|
|
361
|
+
// Handle WS file:changed → invalidate folder. The index is only marked stale, and app-wide
|
|
362
|
+
// (useGlobalEvents, useFileIndexInvalidation) so that also works with this drawer closed;
|
|
363
|
+
// refetching it here was a 22 MB download per change on a large project.
|
|
363
364
|
useEffect(() => {
|
|
364
365
|
if (!activeProject) return;
|
|
365
366
|
const projectName = activeProject.name;
|
|
@@ -389,7 +390,8 @@ export function FileTree({ onFileOpen }: FileTreeProps = {}) {
|
|
|
389
390
|
|
|
390
391
|
// Symmetric with the fsChanged(...) calls this file emits after its own mutations: an
|
|
391
392
|
// explorer window mutating a directory inside this project must invalidate the matching
|
|
392
|
-
// tree node too.
|
|
393
|
+
// tree node too. It is also what marks the index stale after the tree's own renames, creates
|
|
394
|
+
// and moves.
|
|
393
395
|
useEffect(() => {
|
|
394
396
|
if (!activeProject) return;
|
|
395
397
|
const projectName = activeProject.name;
|
|
@@ -398,6 +400,7 @@ export function FileTree({ onFileOpen }: FileTreeProps = {}) {
|
|
|
398
400
|
const relative = relativeProjectPath(root, absoluteDir);
|
|
399
401
|
if (relative == null) return; // outside this project — not the tree's concern
|
|
400
402
|
const store = useFileStore.getState();
|
|
403
|
+
store.markIndexStale(projectName);
|
|
401
404
|
store.invalidateFolder(projectName, relative);
|
|
402
405
|
});
|
|
403
406
|
}, [activeProject]);
|
|
@@ -529,8 +532,6 @@ export function FileTree({ onFileOpen }: FileTreeProps = {}) {
|
|
|
529
532
|
const newPath = parentPath ? `${parentPath}/${value}` : value;
|
|
530
533
|
await api.post(`${projectUrl(projectName)}/files/rename`, { oldPath: node.path, newPath });
|
|
531
534
|
clearInlineAction();
|
|
532
|
-
store.invalidateIndex();
|
|
533
|
-
store.loadIndex(projectName);
|
|
534
535
|
store.invalidateFolder(projectName, parentPath);
|
|
535
536
|
fsChanged(absoluteProjectPath(activeProject!.path, parentPath));
|
|
536
537
|
} else {
|
|
@@ -538,8 +539,6 @@ export function FileTree({ onFileOpen }: FileTreeProps = {}) {
|
|
|
538
539
|
const fullPath = row.targetPath ? `${row.targetPath}/${value}` : value;
|
|
539
540
|
await api.post(`${projectUrl(projectName)}/files/create`, { path: fullPath, type });
|
|
540
541
|
clearInlineAction();
|
|
541
|
-
store.invalidateIndex();
|
|
542
|
-
store.loadIndex(projectName);
|
|
543
542
|
store.invalidateFolder(projectName, row.targetPath);
|
|
544
543
|
fsChanged(absoluteProjectPath(activeProject!.path, row.targetPath));
|
|
545
544
|
}
|
|
@@ -178,8 +178,6 @@ export function useTreeRowDnd(options: TreeRowDndOptions): TreeRowDnd {
|
|
|
178
178
|
api.post(`${projectUrl(projectName)}/files/move`, { source: sourcePath, destination })
|
|
179
179
|
.then(() => {
|
|
180
180
|
const store = useFileStore.getState();
|
|
181
|
-
store.invalidateIndex();
|
|
182
|
-
store.loadIndex(projectName);
|
|
183
181
|
store.invalidateFolder(projectName, sourceParent);
|
|
184
182
|
store.invalidateFolder(projectName, dropTargetDir);
|
|
185
183
|
// An explorer window showing either folder must refresh too.
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* icon/virtualisation deps) never lands in the initial bundle.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import { lazyWithPreload, type PreloadableComponent } from "@/lib/lazy-with-preload";
|
|
11
11
|
import type { WindowKind } from "./window-store-types";
|
|
12
12
|
|
|
13
13
|
export interface WindowContentProps {
|
|
@@ -17,13 +17,13 @@ export interface WindowContentProps {
|
|
|
17
17
|
payload?: Record<string, unknown>;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
export const WINDOW_CONTENT: Record<WindowKind,
|
|
21
|
-
explorer:
|
|
22
|
-
"agent-session":
|
|
23
|
-
"system-monitor":
|
|
24
|
-
"tab-host":
|
|
25
|
-
"remote-desktop":
|
|
26
|
-
settings:
|
|
20
|
+
export const WINDOW_CONTENT: Record<WindowKind, PreloadableComponent<WindowContentProps>> = {
|
|
21
|
+
explorer: lazyWithPreload(() => import("@/components/os-explorer/explorer-window-content")),
|
|
22
|
+
"agent-session": lazyWithPreload(() => import("@/components/chat/agent-session-window-content")),
|
|
23
|
+
"system-monitor": lazyWithPreload(() => import("@/components/system/system-monitor-window-content")),
|
|
24
|
+
"tab-host": lazyWithPreload(() => import("./tab-host-window-content")),
|
|
25
|
+
"remote-desktop": lazyWithPreload(() => import("@/components/remote-desktop/remote-desktop-window-content")),
|
|
26
|
+
settings: lazyWithPreload(() => import("@/components/settings/settings-window-content")),
|
|
27
27
|
};
|
|
28
28
|
|
|
29
29
|
/** Titlebar text for a window. Falls back to the kind's generic name. */
|