@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,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turning viewer gestures into emulator input.
|
|
3
|
+
*
|
|
4
|
+
* The one rule that shapes this file: **`sendKey.text` carries printable ASCII [32,127) and
|
|
5
|
+
* nothing else.** Measured in Phase 0 — `"café"` arrives as `"caf"`, and Vietnamese, emoji and
|
|
6
|
+
* CJK vanish entirely, with no error and no log. So text is split: ASCII goes through `text`,
|
|
7
|
+
* and everything else goes through the clipboard plus a paste combo, which was verified to
|
|
8
|
+
* carry `"Tiếng Việt 😀"` intact into the guest.
|
|
9
|
+
*/
|
|
10
|
+
import { unaryCall, type EmulatorChannel } from "./android-grpc.ts";
|
|
11
|
+
import type { AndroidTouchPoint } from "../../shared/android-protocol.ts";
|
|
12
|
+
|
|
13
|
+
/** The emulator accepts at most this many simultaneous contacts. */
|
|
14
|
+
export const MAX_TOUCH_POINTS = 10;
|
|
15
|
+
|
|
16
|
+
export function isInjectableAscii(text: string): boolean {
|
|
17
|
+
for (const ch of text) {
|
|
18
|
+
const code = ch.codePointAt(0)!;
|
|
19
|
+
if (code < 32 || code >= 127) return false;
|
|
20
|
+
}
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Send one touch update.
|
|
26
|
+
*
|
|
27
|
+
* `pressure: 0` is how a finger is released, and it is mandatory: the proto states an identifier
|
|
28
|
+
* that never sees zero is not unregistered. The emulator does expire a slot on its own, but only
|
|
29
|
+
* after 120 seconds — far too long to rely on, which is why the session has its own release path.
|
|
30
|
+
*/
|
|
31
|
+
export async function sendTouch(channel: EmulatorChannel, touches: AndroidTouchPoint[]): Promise<void> {
|
|
32
|
+
const clamped = touches.slice(0, MAX_TOUCH_POINTS).map((t) => ({
|
|
33
|
+
x: Math.round(t.x),
|
|
34
|
+
y: Math.round(t.y),
|
|
35
|
+
identifier: Math.max(0, Math.round(t.id)),
|
|
36
|
+
pressure: t.pressure > 0 ? Math.max(1, Math.round(t.pressure)) : 0,
|
|
37
|
+
}));
|
|
38
|
+
await unaryCall(channel, "sendTouch", { touches: clamped }, 2000);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Release every contact. Called on blur, tab hide, lost control and disconnect. */
|
|
42
|
+
export async function releaseAllTouches(channel: EmulatorChannel): Promise<void> {
|
|
43
|
+
const release = Array.from({ length: MAX_TOUCH_POINTS }, (_, i) => ({
|
|
44
|
+
x: 0, y: 0, identifier: i, pressure: 0,
|
|
45
|
+
}));
|
|
46
|
+
await unaryCall(channel, "sendTouch", { touches: release }, 2000);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** The w3c `KeyboardEvent.key` values the emulator maps to Android's own buttons. */
|
|
50
|
+
const HARDWARE_KEYS = {
|
|
51
|
+
home: "GoHome",
|
|
52
|
+
back: "GoBack",
|
|
53
|
+
recent: "AppSwitch",
|
|
54
|
+
power: "Power",
|
|
55
|
+
"volume-up": "AudioVolumeUp",
|
|
56
|
+
"volume-down": "AudioVolumeDown",
|
|
57
|
+
} as const;
|
|
58
|
+
|
|
59
|
+
export type HardwareKey = keyof typeof HARDWARE_KEYS;
|
|
60
|
+
|
|
61
|
+
export async function sendHardwareKey(channel: EmulatorChannel, key: HardwareKey): Promise<void> {
|
|
62
|
+
await unaryCall(channel, "sendKey", { key: HARDWARE_KEYS[key], eventType: "keypress" }, 2000);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** `key` takes the browser's own `KeyboardEvent.key` value — the proto follows the w3c list. */
|
|
66
|
+
export async function sendKey(
|
|
67
|
+
channel: EmulatorChannel,
|
|
68
|
+
key: string,
|
|
69
|
+
action: "down" | "up" | "press",
|
|
70
|
+
): Promise<void> {
|
|
71
|
+
const eventType = action === "down" ? "keydown" : action === "up" ? "keyup" : "keypress";
|
|
72
|
+
await unaryCall(channel, "sendKey", { key, eventType }, 2000);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** ASCII only — the caller must have checked. Anything else is silently dropped by the emulator. */
|
|
76
|
+
export async function sendAsciiText(channel: EmulatorChannel, text: string): Promise<void> {
|
|
77
|
+
// The proto warns the keyboard buffer can be overrun by >1 KB at a time, and points at the
|
|
78
|
+
// clipboard for bulk text. Chunking keeps ordinary typing on this path either way.
|
|
79
|
+
for (let i = 0; i < text.length; i += 256) {
|
|
80
|
+
await unaryCall(channel, "sendKey", { text: text.slice(i, i + 256) }, 3000);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* The path for anything not ASCII: put it on the guest clipboard and paste.
|
|
86
|
+
*
|
|
87
|
+
* Verified end to end in Phase 0 — `setClipboard` carried `"Tiếng Việt 😀"` into the guest
|
|
88
|
+
* exactly, confirmed both by reading it back and by Android's own paste suggestion rendering it.
|
|
89
|
+
*/
|
|
90
|
+
export async function pasteText(channel: EmulatorChannel, text: string): Promise<void> {
|
|
91
|
+
await unaryCall(channel, "setClipboard", { text }, 3000);
|
|
92
|
+
await unaryCall(channel, "sendKey", { key: "Control", eventType: "keydown" }, 2000);
|
|
93
|
+
await unaryCall(channel, "sendKey", { key: "v", eventType: "keypress" }, 2000);
|
|
94
|
+
await unaryCall(channel, "sendKey", { key: "Control", eventType: "keyup" }, 2000);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Send text by whichever path can actually carry it. */
|
|
98
|
+
export async function sendText(channel: EmulatorChannel, text: string): Promise<"typed" | "pasted"> {
|
|
99
|
+
if (isInjectableAscii(text)) {
|
|
100
|
+
await sendAsciiText(channel, text);
|
|
101
|
+
return "typed";
|
|
102
|
+
}
|
|
103
|
+
await pasteText(channel, text);
|
|
104
|
+
return "pasted";
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Rotate the guest.
|
|
109
|
+
*
|
|
110
|
+
* `setPhysicalModel` with `ROTATION` takes three angles in degrees as [x, y, z], and the proto's
|
|
111
|
+
* own `Rotation.SkinRotation` enum names what each z angle means: PORTRAIT 0, LANDSCAPE 90,
|
|
112
|
+
* REVERSE_PORTRAIT -180, REVERSE_LANDSCAPE -90. So 270 is sent as **-90**, not 270 — the proto
|
|
113
|
+
* documents the angle as being in [-180, 180] and a value outside it has no defined meaning.
|
|
114
|
+
*
|
|
115
|
+
* Nothing here tracks the resulting orientation: every screenshot frame carries the emulator's
|
|
116
|
+
* own `format.rotation`, which is the authority (see `android-video.ts`).
|
|
117
|
+
*/
|
|
118
|
+
export async function setRotation(channel: EmulatorChannel, degrees: 0 | 90 | 180 | 270): Promise<void> {
|
|
119
|
+
const z = degrees === 270 ? -90 : degrees;
|
|
120
|
+
await unaryCall(channel, "setPhysicalModel", {
|
|
121
|
+
target: "ROTATION",
|
|
122
|
+
value: { data: [0, 0, z] },
|
|
123
|
+
}, 3000);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export async function getClipboard(channel: EmulatorChannel): Promise<string> {
|
|
127
|
+
const res = await unaryCall(channel, "getClipboard", {}, 3000);
|
|
128
|
+
return typeof res?.text === "string" ? res.text : "";
|
|
129
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-device logcat: one gRPC stream, many watchers, a bounded ring buffer and nothing on disk.
|
|
3
|
+
*
|
|
4
|
+
* Plan §Phase 3 is specific about the shape: filter, pause, clear-view and a ring buffer, with
|
|
5
|
+
* **nothing written to the database**. So this holds a fixed number of entries in memory and
|
|
6
|
+
* hands a new watcher that backlog; filtering, pausing and clearing the view are the client's,
|
|
7
|
+
* because all three are about what one person is looking at, not about what the device said.
|
|
8
|
+
*
|
|
9
|
+
* ## `sort: Parsed` does not work, and fails by being silently empty
|
|
10
|
+
*
|
|
11
|
+
* The proto offers `LogMessage.LogType.Parsed`, which would deliver `LogcatEntry` records with
|
|
12
|
+
* level, tag, pid and tid already separated — exactly what this file wants. Measured against
|
|
13
|
+
* emulator **36.5.10.0** it is not implemented:
|
|
14
|
+
*
|
|
15
|
+
* - `streamLogcat({sort: "Parsed"})` → **334 messages in 8 seconds, every one of them with an
|
|
16
|
+
* empty `entries` list AND an empty `contents` string**, each echoing `sort: Text` back. Not
|
|
17
|
+
* an error, not `UNIMPLEMENTED`: a busy stream of nothing, which reads exactly like a device
|
|
18
|
+
* that is not logging.
|
|
19
|
+
* - The same with the numeric enum value (`sort: 1`) — 57 empty messages in 8 seconds.
|
|
20
|
+
* - `streamLogcat({})` — the default, `Text` — delivers the real log.
|
|
21
|
+
* - The deprecated unary `getLogcat` answers **12 UNIMPLEMENTED** outright, so there is no
|
|
22
|
+
* snapshot RPC either; the ring buffer is the only backlog there is.
|
|
23
|
+
*
|
|
24
|
+
* So the lines are parsed here. Two more measured details that shape the parser: each gRPC
|
|
25
|
+
* message carries **exactly one line** with **no trailing newline** (so nothing has to buffer a
|
|
26
|
+
* partial line across messages — though the split still handles it, since a future build
|
|
27
|
+
* batching lines would otherwise silently concatenate them into one entry), and the format is
|
|
28
|
+
* logcat's `threadtime`, which carries no year.
|
|
29
|
+
*/
|
|
30
|
+
import type { EmulatorChannel } from "./android-grpc.ts";
|
|
31
|
+
import type { AndroidLogEntry, AndroidLogLevel } from "../../shared/android-protocol.ts";
|
|
32
|
+
|
|
33
|
+
/** Enough to scroll back through a crash, far too little to be a storage decision. */
|
|
34
|
+
export const LOGCAT_RING_SIZE = 2_000;
|
|
35
|
+
|
|
36
|
+
/** logcat's one-letter levels. `A` is assert, which Android also calls FATAL. */
|
|
37
|
+
const LEVELS: Record<string, AndroidLogLevel> = {
|
|
38
|
+
V: "verbose", D: "debug", I: "info", W: "warn", E: "error", F: "fatal", A: "fatal", S: "verbose",
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/** `MM-DD HH:MM:SS.mmm PID TID L TAG: message` — logcat's `threadtime`, which is the default. */
|
|
42
|
+
const THREADTIME = /^(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})\.(\d{3})\s+(\d+)\s+(\d+)\s+([A-Z])\s+([^:]*?)\s*:\s?(.*)$/;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Turn one logcat line into an entry.
|
|
46
|
+
*
|
|
47
|
+
* A line that does not parse is **kept**, not dropped: it is a continuation of the line above
|
|
48
|
+
* (a stack trace written with embedded newlines) or a banner like `--------- beginning of main`,
|
|
49
|
+
* and losing either would quietly cut a crash in half. It gets no level and no tag rather than
|
|
50
|
+
* an invented one.
|
|
51
|
+
*/
|
|
52
|
+
export function parseLogcatLine(line: string, id: number, now = new Date()): AndroidLogEntry | null {
|
|
53
|
+
if (line.length === 0) return null;
|
|
54
|
+
const m = THREADTIME.exec(line);
|
|
55
|
+
if (!m) return { id, timestamp: now.getTime(), pid: 0, tid: 0, level: "verbose", tag: "", message: line };
|
|
56
|
+
|
|
57
|
+
const [, mo, day, hh, mm, ss, ms, pid, tid, level, tag, message] = m;
|
|
58
|
+
// threadtime has no year. Assume this one, and step back if that puts the line in the future
|
|
59
|
+
// by more than a day — which is what reading a December log on January 1st looks like.
|
|
60
|
+
let at = new Date(now.getFullYear(), Number(mo) - 1, Number(day), Number(hh), Number(mm), Number(ss), Number(ms));
|
|
61
|
+
if (at.getTime() - now.getTime() > 24 * 60 * 60_000) at = new Date(at.getTime() - 365 * 24 * 60 * 60_000);
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
id,
|
|
65
|
+
timestamp: at.getTime(),
|
|
66
|
+
pid: Number(pid),
|
|
67
|
+
tid: Number(tid),
|
|
68
|
+
level: LEVELS[level!] ?? "verbose",
|
|
69
|
+
tag: tag ?? "",
|
|
70
|
+
message: message ?? "",
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export type LogcatListener = (entries: AndroidLogEntry[]) => void;
|
|
75
|
+
|
|
76
|
+
interface DeviceLogcat {
|
|
77
|
+
channel: EmulatorChannel;
|
|
78
|
+
/** The live gRPC stream, or null while nobody is watching. */
|
|
79
|
+
call: any;
|
|
80
|
+
ring: AndroidLogEntry[];
|
|
81
|
+
listeners: Set<LogcatListener>;
|
|
82
|
+
nextId: number;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const streams = new Map<string, DeviceLogcat>();
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Start watching a device's logcat.
|
|
89
|
+
*
|
|
90
|
+
* The channel is the caller's and is NOT closed here: it belongs to the session, which may still
|
|
91
|
+
* be sending input on it long after the log panel is closed.
|
|
92
|
+
*/
|
|
93
|
+
export function subscribeLogcat(
|
|
94
|
+
deviceId: string,
|
|
95
|
+
channel: EmulatorChannel,
|
|
96
|
+
listener: LogcatListener,
|
|
97
|
+
): () => void {
|
|
98
|
+
let stream = streams.get(deviceId);
|
|
99
|
+
if (!stream) {
|
|
100
|
+
stream = { channel, call: null, ring: [], listeners: new Set(), nextId: 1 };
|
|
101
|
+
streams.set(deviceId, stream);
|
|
102
|
+
}
|
|
103
|
+
// A session that reconnected has a new channel; the ring belongs to the device, not to it.
|
|
104
|
+
stream.channel = channel;
|
|
105
|
+
if (!stream.call) openStream(deviceId, stream);
|
|
106
|
+
stream.listeners.add(listener);
|
|
107
|
+
// Hand over what has already been said, so opening the panel does not show an empty box on a
|
|
108
|
+
// device that has been running for an hour.
|
|
109
|
+
if (stream.ring.length > 0) listener([...stream.ring]);
|
|
110
|
+
|
|
111
|
+
return () => {
|
|
112
|
+
const s = streams.get(deviceId);
|
|
113
|
+
if (!s) return;
|
|
114
|
+
s.listeners.delete(listener);
|
|
115
|
+
// Plan gate: "logs hidden ngừng subscription". Nobody watching means nothing streaming.
|
|
116
|
+
if (s.listeners.size === 0) closeStream(deviceId);
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function openStream(deviceId: string, stream: DeviceLogcat): void {
|
|
121
|
+
// No `sort` field: the default is Text, and Parsed is the empty-stream trap described above.
|
|
122
|
+
const call = stream.channel.client.streamLogcat({}, stream.channel.metadata);
|
|
123
|
+
stream.call = call;
|
|
124
|
+
call.on("data", (msg: any) => {
|
|
125
|
+
const contents: string = msg?.contents ?? "";
|
|
126
|
+
if (contents.length === 0) return;
|
|
127
|
+
const entries: AndroidLogEntry[] = [];
|
|
128
|
+
for (const line of contents.split("\n")) {
|
|
129
|
+
const entry = parseLogcatLine(line.replace(/\r$/, ""), stream.nextId);
|
|
130
|
+
if (entry) { stream.nextId++; entries.push(entry); }
|
|
131
|
+
}
|
|
132
|
+
if (entries.length === 0) return;
|
|
133
|
+
stream.ring.push(...entries);
|
|
134
|
+
if (stream.ring.length > LOGCAT_RING_SIZE) {
|
|
135
|
+
stream.ring.splice(0, stream.ring.length - LOGCAT_RING_SIZE);
|
|
136
|
+
}
|
|
137
|
+
for (const l of stream.listeners) l(entries);
|
|
138
|
+
});
|
|
139
|
+
call.on("error", (e: any) => {
|
|
140
|
+
// code 1 is CANCELLED, which is how the last watcher leaving ends it.
|
|
141
|
+
if (e?.code !== 1) console.warn(`[android] logcat stream for ${deviceId} failed: ${e?.details ?? e?.message ?? e}`);
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Stop the gRPC stream but **keep the ring**.
|
|
147
|
+
*
|
|
148
|
+
* The plan's gate is that hiding the log stops the subscription, which this does. Throwing the
|
|
149
|
+
* backlog away as well is a different thing and a worse one: closing the panel to read a stack
|
|
150
|
+
* trace on another screen and reopening it would show an empty box. The cost is that a reopened
|
|
151
|
+
* panel has a gap where nothing was being watched — the timestamps say so.
|
|
152
|
+
*/
|
|
153
|
+
function closeStream(deviceId: string): void {
|
|
154
|
+
const stream = streams.get(deviceId);
|
|
155
|
+
if (!stream?.call) return;
|
|
156
|
+
try { stream.call.cancel(); } catch { /* already gone */ }
|
|
157
|
+
stream.call = null;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Tear everything down for a device — the session closing, or the emulator stopping. */
|
|
161
|
+
export function closeDeviceLogcat(deviceId: string): void {
|
|
162
|
+
closeStream(deviceId);
|
|
163
|
+
streams.delete(deviceId);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** What a watcher would be handed right now. Exposed for tests and for a snapshot download. */
|
|
167
|
+
export function logcatBacklog(deviceId: string): AndroidLogEntry[] {
|
|
168
|
+
return [...(streams.get(deviceId)?.ring ?? [])];
|
|
169
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single-use, short-lived nonces for `/ws/android`.
|
|
3
|
+
*
|
|
4
|
+
* The coarse PPM token already travels as `?token=` on every WS URL and proves only "holds the
|
|
5
|
+
* one reusable app token". A session that hands over touch and keyboard control of a running
|
|
6
|
+
* VM needs more than that, so `POST /api/android/devices/:id/sessions` mints a nonce that must
|
|
7
|
+
* be the client's *first* WS message and can be spent exactly once.
|
|
8
|
+
*/
|
|
9
|
+
const TTL_MS = 30_000;
|
|
10
|
+
const MAX_OUTSTANDING = 32;
|
|
11
|
+
|
|
12
|
+
interface Nonce {
|
|
13
|
+
value: string;
|
|
14
|
+
deviceId: string;
|
|
15
|
+
expiresAt: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const outstanding = new Map<string, Nonce>();
|
|
19
|
+
|
|
20
|
+
function sweep(): void {
|
|
21
|
+
const now = Date.now();
|
|
22
|
+
for (const [k, n] of outstanding) if (n.expiresAt <= now) outstanding.delete(k);
|
|
23
|
+
// A client that mints and never connects must not be able to grow this without bound.
|
|
24
|
+
while (outstanding.size > MAX_OUTSTANDING) {
|
|
25
|
+
const oldest = [...outstanding.values()].sort((a, b) => a.expiresAt - b.expiresAt)[0];
|
|
26
|
+
if (!oldest) break;
|
|
27
|
+
outstanding.delete(oldest.value);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function mintAndroidNonce(deviceId: string): string {
|
|
32
|
+
sweep();
|
|
33
|
+
const value = crypto.randomUUID().replace(/-/g, "");
|
|
34
|
+
outstanding.set(value, { value, deviceId, expiresAt: Date.now() + TTL_MS });
|
|
35
|
+
return value;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Spend a nonce. Returns the device it was minted for, or null if unknown/expired/already used. */
|
|
39
|
+
export function consumeAndroidNonce(value: string): string | null {
|
|
40
|
+
sweep();
|
|
41
|
+
const n = outstanding.get(value);
|
|
42
|
+
if (!n) return null;
|
|
43
|
+
outstanding.delete(value); // single use, even if the rest of the handshake fails
|
|
44
|
+
return n.expiresAt > Date.now() ? n.deviceId : null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Revoke everything — used when the PPM token changes or auth is turned off. */
|
|
48
|
+
export function revokeAllAndroidNonces(): void {
|
|
49
|
+
outstanding.clear();
|
|
50
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Operation IDs for anything that takes longer than a request should wait for.
|
|
3
|
+
*
|
|
4
|
+
* Plan §5: "Các thao tác lâu trả operation ID, không giữ HTTP request chờ boot nhiều phút." A
|
|
5
|
+
* cold boot is minutes; an HTTP request held that long dies to some proxy's idle timeout and the
|
|
6
|
+
* client cannot tell a lost connection from a failed boot. So start returns an id and the client
|
|
7
|
+
* polls — and a client that reconnects mid-boot re-attaches rather than starting a second one.
|
|
8
|
+
*/
|
|
9
|
+
export type OperationState = "pending" | "running" | "succeeded" | "failed" | "cancelled";
|
|
10
|
+
|
|
11
|
+
export interface Operation<T = unknown> {
|
|
12
|
+
id: string;
|
|
13
|
+
kind: string;
|
|
14
|
+
state: OperationState;
|
|
15
|
+
/** Human-readable progress, e.g. "booting (28s)". Not a percentage: boot has no honest one. */
|
|
16
|
+
detail: string;
|
|
17
|
+
startedAt: number;
|
|
18
|
+
endedAt: number | null;
|
|
19
|
+
result: T | null;
|
|
20
|
+
error: string | null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Finished operations are kept this long so a client that reconnects can still read the outcome. */
|
|
24
|
+
const RETAIN_FINISHED_MS = 5 * 60_000;
|
|
25
|
+
const MAX_OPERATIONS = 200;
|
|
26
|
+
|
|
27
|
+
const operations = new Map<string, Operation<any>>();
|
|
28
|
+
|
|
29
|
+
function sweep(): void {
|
|
30
|
+
const now = Date.now();
|
|
31
|
+
for (const [id, op] of operations) {
|
|
32
|
+
if (op.endedAt !== null && now - op.endedAt > RETAIN_FINISHED_MS) operations.delete(id);
|
|
33
|
+
}
|
|
34
|
+
// Hard cap so a pathological caller cannot grow this without bound.
|
|
35
|
+
while (operations.size > MAX_OPERATIONS) {
|
|
36
|
+
const oldest = [...operations.values()].sort((a, b) => a.startedAt - b.startedAt)[0];
|
|
37
|
+
if (!oldest) break;
|
|
38
|
+
operations.delete(oldest.id);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function createOperation<T>(kind: string, detail = ""): Operation<T> {
|
|
43
|
+
sweep();
|
|
44
|
+
const op: Operation<T> = {
|
|
45
|
+
id: crypto.randomUUID(),
|
|
46
|
+
kind,
|
|
47
|
+
state: "pending",
|
|
48
|
+
detail,
|
|
49
|
+
startedAt: Date.now(),
|
|
50
|
+
endedAt: null,
|
|
51
|
+
result: null,
|
|
52
|
+
error: null,
|
|
53
|
+
};
|
|
54
|
+
operations.set(op.id, op);
|
|
55
|
+
return op;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function getOperation(id: string): Operation | null {
|
|
59
|
+
sweep();
|
|
60
|
+
return operations.get(id) ?? null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function updateOperation(id: string, patch: Partial<Pick<Operation, "state" | "detail">>): void {
|
|
64
|
+
const op = operations.get(id);
|
|
65
|
+
if (!op) return;
|
|
66
|
+
Object.assign(op, patch);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function finishOperation<T>(id: string, result: T): void {
|
|
70
|
+
const op = operations.get(id);
|
|
71
|
+
if (!op) return;
|
|
72
|
+
cancellers.delete(id);
|
|
73
|
+
if (op.state === "cancelled") return; // a cancel that landed first wins
|
|
74
|
+
op.state = "succeeded";
|
|
75
|
+
op.result = result;
|
|
76
|
+
op.endedAt = Date.now();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function failOperation(id: string, error: string): void {
|
|
80
|
+
const op = operations.get(id);
|
|
81
|
+
if (!op) return;
|
|
82
|
+
cancellers.delete(id);
|
|
83
|
+
if (op.state === "cancelled") return;
|
|
84
|
+
op.state = "failed";
|
|
85
|
+
op.error = error;
|
|
86
|
+
op.endedAt = Date.now();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Test seam: drop everything. */
|
|
90
|
+
export function _resetOperations(): void {
|
|
91
|
+
operations.clear();
|
|
92
|
+
cancellers.clear();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/* ---------------------------------------------------------------------------------------------
|
|
96
|
+
* Cancelling.
|
|
97
|
+
*
|
|
98
|
+
* An install can be cancelled where a boot cannot, so cancellation is registered per operation
|
|
99
|
+
* rather than assumed: `getOperation` reports `cancellable` and the route refuses to cancel one
|
|
100
|
+
* that never registered a way to stop.
|
|
101
|
+
* ------------------------------------------------------------------------------------------- */
|
|
102
|
+
|
|
103
|
+
const cancellers = new Map<string, () => void>();
|
|
104
|
+
|
|
105
|
+
export function registerCanceller(id: string, cancel: () => void): void {
|
|
106
|
+
if (operations.has(id)) cancellers.set(id, cancel);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function isCancellable(id: string): boolean {
|
|
110
|
+
return cancellers.has(id);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Returns false when the operation does not exist, has already ended, or cannot be cancelled. */
|
|
114
|
+
export function cancelOperation(id: string): boolean {
|
|
115
|
+
const op = operations.get(id);
|
|
116
|
+
const cancel = cancellers.get(id);
|
|
117
|
+
if (!op || !cancel || op.endedAt !== null) return false;
|
|
118
|
+
cancellers.delete(id);
|
|
119
|
+
op.state = "cancelled";
|
|
120
|
+
op.detail = "cancelled";
|
|
121
|
+
op.endedAt = Date.now();
|
|
122
|
+
try { cancel(); } catch { /* the work was already finishing */ }
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A still of the device screen, and the clipboard both ways.
|
|
3
|
+
*
|
|
4
|
+
* Deliberately **PNG** rather than the raw RGBA the video pipeline consumes: the emulator encodes
|
|
5
|
+
* it itself, so nothing here has to know the buffer layout — and that layout is exactly what the
|
|
6
|
+
* proto's own comment gets wrong (it says "bottom up"; measured against `adb exec-out screencap`
|
|
7
|
+
* it is top-down, see the Phase 0 report §5.3). A file the user downloads is not the place to
|
|
8
|
+
* rediscover that.
|
|
9
|
+
*
|
|
10
|
+
* `getScreenshot` is a *unary* call, so it is independent of the session's `streamScreenshot` —
|
|
11
|
+
* measured in Phase 2: 343 frames before, 358 after, i.e. taking a still does not disturb a
|
|
12
|
+
* running stream.
|
|
13
|
+
*/
|
|
14
|
+
import { unaryCall, type EmulatorChannel } from "./android-grpc.ts";
|
|
15
|
+
|
|
16
|
+
export interface Screenshot {
|
|
17
|
+
png: Uint8Array;
|
|
18
|
+
width: number;
|
|
19
|
+
height: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* `width`/`height` in `ImageFormat` are a **bounding box** that preserves the device's aspect
|
|
24
|
+
* ratio, not an exact size (Phase 0 §5.4). Omitting them is what asks for the native resolution,
|
|
25
|
+
* which is what a screenshot should be.
|
|
26
|
+
*/
|
|
27
|
+
export async function takeScreenshot(channel: EmulatorChannel, timeoutMs = 10_000): Promise<Screenshot> {
|
|
28
|
+
const img = await unaryCall<any>(channel, "getScreenshot", { format: "PNG" }, timeoutMs);
|
|
29
|
+
const png: Uint8Array = img?.image instanceof Uint8Array ? img.image : new Uint8Array(img?.image ?? []);
|
|
30
|
+
if (png.length === 0) throw new Error("the emulator returned an empty screenshot");
|
|
31
|
+
return {
|
|
32
|
+
png,
|
|
33
|
+
// `format.width/height` is the authority; the top-level pair is deprecated in the proto.
|
|
34
|
+
width: Number(img?.format?.width ?? img?.width ?? 0),
|
|
35
|
+
height: Number(img?.format?.height ?? img?.height ?? 0),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** A filename a person can find again: the AVD, then the local date and time. */
|
|
40
|
+
export function screenshotFilename(avdName: string, at = new Date()): string {
|
|
41
|
+
const p = (n: number) => String(n).padStart(2, "0");
|
|
42
|
+
const stamp = `${at.getFullYear()}${p(at.getMonth() + 1)}${p(at.getDate())}-${p(at.getHours())}${p(at.getMinutes())}${p(at.getSeconds())}`;
|
|
43
|
+
const safe = avdName.replace(/[^A-Za-z0-9._-]+/g, "_").replace(/^_+|_+$/g, "") || "device";
|
|
44
|
+
return `${safe}-${stamp}.png`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Longer than this and it is not a clipboard, it is a file transfer. */
|
|
48
|
+
export const MAX_CLIPBOARD_CHARS = 64 * 1024;
|
|
49
|
+
|
|
50
|
+
export async function setDeviceClipboard(channel: EmulatorChannel, text: string): Promise<void> {
|
|
51
|
+
if (text.length > MAX_CLIPBOARD_CHARS) throw new Error("clipboard text is too long");
|
|
52
|
+
await unaryCall(channel, "setClipboard", { text }, 3000);
|
|
53
|
+
}
|