@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,203 @@
|
|
|
1
|
+
# Phase 3 report — Cài APK, chụp màn hình, logcat
|
|
2
|
+
|
|
3
|
+
Ngày: 2026-09-21. Tiếp sau `android-emulator-phase2-report-260921.md`.
|
|
4
|
+
Bản này ghi **những gì đo được**, trong đó có một RPC mà proto mô tả đầy đủ nhưng emulator
|
|
5
|
+
**không cài đặt** và hỏng theo kiểu im lặng, cùng một chỗ chính tôi viết comment ngược với code.
|
|
6
|
+
|
|
7
|
+
## 1. Kết quả
|
|
8
|
+
|
|
9
|
+
| | |
|
|
10
|
+
|---|---|
|
|
11
|
+
| E2E mức dịch vụ (`tests/e2e/android-phase3-e2e.ts`) | **35/35** |
|
|
12
|
+
| E2E qua server thật (`tests/e2e/android-ws-e2e.ts`) | **35/35** (17 check mới của Phase 3) |
|
|
13
|
+
| E2E hai máy (`tests/e2e/android-two-device-install-e2e.ts`) | **9/9** |
|
|
14
|
+
| Unit mới | **48** (logcat parse 8, apk 16, log filter 18, screenshot download 6) — cộng dồn **116** test Android trên 13 file |
|
|
15
|
+
| `tsc --noEmit` | 0 lỗi |
|
|
16
|
+
| `bun run build:web` | chunk `android-tab-*.js` 37.17 kB (gzip 11.16) — vẫn lazy, không ai trả giá nếu không mở tab |
|
|
17
|
+
|
|
18
|
+
Số đo có ý nghĩa:
|
|
19
|
+
|
|
20
|
+
| | |
|
|
21
|
+
|---|---|
|
|
22
|
+
| Upload + install thật qua HTTP | **78.75 MB**, upload → operation → `succeeded`, staging sạch sau đó |
|
|
23
|
+
| Screenshot PNG | 1.87–1.94 MB, 1080×2400, emulator tự encode |
|
|
24
|
+
| Logcat qua WS | 14 batch / 82 entry trong ~4 s, cửa sổ gộp 120 ms |
|
|
25
|
+
| `getScreenshot` khi stream video đang chạy | 90 → 164 source frame, **không làm gián đoạn** |
|
|
26
|
+
|
|
27
|
+
## 2. `sort: Parsed` không tồn tại, và nó hỏng bằng cách im lặng
|
|
28
|
+
|
|
29
|
+
`emulator_controller.proto` có `LogMessage.LogType.Parsed`, trả về `LogcatEntry` đã tách sẵn
|
|
30
|
+
level, tag, pid, tid — đúng thứ panel cần. Đo trên **emulator 36.5.10.0**:
|
|
31
|
+
|
|
32
|
+
| Yêu cầu | Kết quả trong 8 giây |
|
|
33
|
+
|---|---|
|
|
34
|
+
| `streamLogcat({sort: "Parsed"})` | **334 message**, `entries` rỗng **và** `contents` rỗng, mỗi reply echo `sort: Text` |
|
|
35
|
+
| `streamLogcat({sort: 1})` (enum số) | 57 message, cũng rỗng hoàn toàn |
|
|
36
|
+
| `streamLogcat({})` (mặc định `Text`) | 41 message, **18 083 ký tự log thật** |
|
|
37
|
+
| `getLogcat` (unary, đã deprecated) | **`12 UNIMPLEMENTED`** |
|
|
38
|
+
|
|
39
|
+
Điểm đáng ghi không phải "Parsed hỏng" mà là **cách nó hỏng**: không lỗi, không `UNIMPLEMENTED`,
|
|
40
|
+
chỉ là một stream bận rộn chở không có gì. Nhìn từ UI nó giống hệt một máy không log. Nếu tin
|
|
41
|
+
theo proto thì panel logcat sẽ trống vĩnh viễn trên mọi máy và không có chỗ nào để bắt đầu tìm.
|
|
42
|
+
|
|
43
|
+
Hệ quả thiết kế: `android-logcat.ts` xin `Text` và **tự parse**. Hai chi tiết nữa đo được, và
|
|
44
|
+
chúng quyết định hình dạng parser:
|
|
45
|
+
|
|
46
|
+
* Mỗi message gRPC mang **đúng một dòng**, **không có `\n` cuối** — nên không cần buffer dòng dở.
|
|
47
|
+
(Vẫn `split("\n")` phòng bản emulator sau gộp dòng, vì nếu gộp mà không split thì nhiều dòng
|
|
48
|
+
sẽ dính thành một entry, lại là một lỗi im lặng nữa.)
|
|
49
|
+
* Định dạng là `threadtime` của logcat: `MM-DD HH:MM:SS.mmm PID TID L TAG: message` — **không có
|
|
50
|
+
năm**. Parser lấy năm hiện tại, và lùi một năm nếu như thế đẩy dòng log ra tương lai quá một
|
|
51
|
+
ngày (đọc log tháng 12 vào ngày 1 tháng 1).
|
|
52
|
+
|
|
53
|
+
Dòng không parse được thì **giữ nguyên**, không bỏ: đó là dòng nối của stack trace hoặc banner
|
|
54
|
+
`--------- beginning of main`, mất dòng nào cũng là cắt đôi một crash.
|
|
55
|
+
|
|
56
|
+
### 2.1 Lệch so với plan, có chủ ý
|
|
57
|
+
|
|
58
|
+
Plan §4 viết "ADB dùng discovery/boot readiness, cài APK và logcat". Logcat ở đây đi qua **gRPC
|
|
59
|
+
`streamLogcat`**, không phải `adb logcat`. Lý do:
|
|
60
|
+
|
|
61
|
+
* gRPC dùng lại đúng channel đã xác thực của session — không thêm một tiến trình con mỗi máy.
|
|
62
|
+
* `adb logcat` cần adb serial, mà serial là `null` trong lúc máy còn boot; channel gRPC thì đã có.
|
|
63
|
+
* Theo chính comment trong proto, `streamLogcat` **chạy `logcat` trong guest qua `AdbShellStream`**
|
|
64
|
+
— tức dữ liệu là một, chỉ khác đường vận chuyển.
|
|
65
|
+
|
|
66
|
+
Cái mất: không đẩy được filter xuống thiết bị (`adb logcat -s TAG:E`), nên mọi dòng đều qua dây
|
|
67
|
+
rồi mới lọc ở client. Đo thực tế ~40 message/8 s nên chưa đáng đổi. Cài APK thì **vẫn đi adb**
|
|
68
|
+
đúng như plan, vì `EmulatorController` không có RPC install nào cả.
|
|
69
|
+
|
|
70
|
+
## 3. `adb install` không chọn bừa — nó từ chối
|
|
71
|
+
|
|
72
|
+
Comment đầu tiên tôi viết trong `android-apk.ts` nói `adb install` không `-s` sẽ "chọn máy nào
|
|
73
|
+
tuỳ nó thích". Đo với hai emulator đang chạy:
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
$ adb install <file>
|
|
77
|
+
adb: more than one device/emulator
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Nó **từ chối, không cài gì cả**. Sửa lại comment. Điều này làm `-s` còn quan trọng hơn: trên máy
|
|
81
|
+
một emulator thì thiếu `-s` vẫn chạy **nhờ may**, và hỏng đúng vào ngày người dùng mở máy thứ hai.
|
|
82
|
+
|
|
83
|
+
Gate "install đúng target khi có hai máy" được chứng minh bằng
|
|
84
|
+
`tests/e2e/android-two-device-install-e2e.ts`, 9/9. Chỗ khó là **làm cho test có thể fail**: hai
|
|
85
|
+
AVD spike dùng chung một system image nên danh sách package giống hệt nhau, và cài một gói cả
|
|
86
|
+
hai đều có sẵn thì pass dù `-s` có được tôn trọng hay không. Test tự tạo chênh lệch bằng
|
|
87
|
+
`pm uninstall --user 0 com.google.android.deskclock` trên máy đích (`--user 0` vì đây là app hệ
|
|
88
|
+
thống, uninstall thường bị từ chối), rồi kiểm tra cả hai máy trước và sau, và trả lại nguyên trạng.
|
|
89
|
+
|
|
90
|
+
## 4. Huỷ upload không để lại temp
|
|
91
|
+
|
|
92
|
+
Gate của plan. Không tự nhiên mà có: request bị abort làm phép đọc **fail giữa chừng file**, nên
|
|
93
|
+
phần đã ghi phải bị xoá trên mọi nhánh lỗi.
|
|
94
|
+
|
|
95
|
+
`stageApkUpload` xoá file dở khi abort, khi quá hạn mức, và khi header không phải zip — kiểm tra
|
|
96
|
+
4 byte đầu **trong lúc** stream, nên một body 5 GB bị cắt tại ngưỡng chứ không phải sau khi đã
|
|
97
|
+
ghi xong. PPM bị kill giữa chừng upload thì `sweepApkStaging()` dọn ở lần sau (quá 1 giờ).
|
|
98
|
+
|
|
99
|
+
Đo qua server thật: abort giữa upload → `AbortError`, thư mục staging **0 file**; upload 78.75 MB
|
|
100
|
+
thành công → install `succeeded` → staging lại **0 file**.
|
|
101
|
+
|
|
102
|
+
## 5. Giết tiến trình **không** làm đóng pipe của nó
|
|
103
|
+
|
|
104
|
+
Deadline cho `adb install` (plan §7 yêu cầu) tưởng là một dòng `setTimeout(() => proc.kill())`.
|
|
105
|
+
Test bằng một `adb` giả là script `#!/bin/sh\nsleep 30`: kill xong, `reader.read()` **vẫn không
|
|
106
|
+
bao giờ resolve** — `sh` chết nhưng `sleep` con thừa kế pipe và giữ nó mở. Test timeout 5 s.
|
|
107
|
+
|
|
108
|
+
Tức là bản đầu tiên của tôi có deadline nhưng deadline không thoát được: hàm vẫn treo đúng chỗ nó
|
|
109
|
+
định cứu. Sửa bằng cùng hình dạng `Encoder.stop()` trong `android-video.ts` đã phải dùng cho
|
|
110
|
+
ffmpeg — **race**, không chờ:
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
const outcome = await Promise.race([collected, expired, aborted]);
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
`cancelled` cũng phải nằm trong race vì cùng một lý do: một cancel chờ read kết thúc sẽ treo tới
|
|
117
|
+
tận deadline 10 phút thay vì trả lời ngay. Hai unit test giữ cả hai nhánh, và cả hai đều **đo thời
|
|
118
|
+
gian** chứ không chỉ đo kết quả — một bản sửa sai vẫn trả đúng message, chỉ là sau 10 phút.
|
|
119
|
+
|
|
120
|
+
## 6. `DeviceEntry.state` không bao giờ báo `booting`
|
|
121
|
+
|
|
122
|
+
Plan §6 nói "install APK chỉ khi guest ready". Cách hiển nhiên là `if (device.state !== "ready")`.
|
|
123
|
+
Nhưng `listDevices` là hàm **đồng bộ** và đặt `state: e ? "ready" : "stopped"` — tức mọi máy có
|
|
124
|
+
tiến trình đều là `ready`, và giá trị `"booting"` khai báo ở dòng 27 của `device-registry.ts`
|
|
125
|
+
chưa từng được sinh ra. Check đó là một tautology.
|
|
126
|
+
|
|
127
|
+
Câu trả lời thật là `getStatus().booted` của chính emulator, tốn một RPC trước mỗi lần install.
|
|
128
|
+
Đây là lỗ hổng của Phase 1, không sửa ở Phase 3 vì làm `listDevices` thành async sẽ chạm mọi
|
|
129
|
+
caller; ghi lại ở đây để Phase 4 xử lý khi làm device manager.
|
|
130
|
+
|
|
131
|
+
## 7. Chỗ tôi viết comment ngược với code, và e2e bắt được
|
|
132
|
+
|
|
133
|
+
`case "logcat"` trong `android-session.ts` nằm **dưới** cổng kiểm tra lease, trong khi comment
|
|
134
|
+
ngay trên nó viết "Not gated on the controller lease". Tức là viewer không giữ lease — đúng
|
|
135
|
+
người cần đọc log khi người khác đang điều khiển — bị từ chối im lặng.
|
|
136
|
+
|
|
137
|
+
Không unit test nào bắt được: nó chỉ sai khi có **hai** viewer và lease đã chuyển. `android-ws-e2e.ts`
|
|
138
|
+
gửi `logcat` từ viewer A **sau khi** B đã cướp lease, và trả về `0 batches, 0 entries`. Đây là lý
|
|
139
|
+
do e2e hai-viewer đáng viết chứ không phải là thừa.
|
|
140
|
+
|
|
141
|
+
## 8. Quyết định thiết kế
|
|
142
|
+
|
|
143
|
+
* **Ring buffer sống qua lần đóng panel.** Gate nói "logs hidden ngừng subscription" — stream gRPC
|
|
144
|
+
dừng thật. Nhưng vứt luôn backlog là chuyện khác và tệ hơn: đóng panel để đọc stack trace ở chỗ
|
|
145
|
+
khác rồi mở lại sẽ thấy hộp rỗng. Ring (2000 entry) chỉ mất khi session của máy đóng. Giá phải
|
|
146
|
+
trả: khoảng trống trong log ở quãng không ai xem — timestamp nói rõ điều đó.
|
|
147
|
+
* **Batch log 120 ms.** Một WS frame mỗi message gRPC sẽ vừa ngập socket vừa tranh chỗ với video
|
|
148
|
+
trên cùng một dây. Viewer nào đang tắc socket thì bỏ qua batch — ring ở server giữ lại rồi.
|
|
149
|
+
* **Screenshot là PNG, không phải RGBA thô.** Emulator tự encode, nên không chỗ nào ở đây phải
|
|
150
|
+
biết layout buffer — mà đó đúng là chỗ proto nói sai (Phase 2 §2.1: proto ghi "bottom up",
|
|
151
|
+
đo ra top-down). File người dùng tải về không phải chỗ để phát hiện lại chuyện đó.
|
|
152
|
+
* **Panel công cụ nằm **dưới** màn hình ở mọi kích thước.** Không phải nhượng bộ cho mobile: log
|
|
153
|
+
phải đọc được *đồng thời* với việc chạm vào guest, mà modal thì không — và dưới cùng cũng là
|
|
154
|
+
thumb zone mà design guidelines yêu cầu, nên layout điện thoại ra từ cùng một quyết định.
|
|
155
|
+
* **Không panel nào được mount khi tab của nó đóng**, nên subscription logcat bám theo panel:
|
|
156
|
+
chuyển sang tab Install là dừng stream log thật sự.
|
|
157
|
+
* **Cài từ project nhận `{project, path}`, không nhận đường dẫn tuyệt đối.** Nhận tuyệt đối thì
|
|
158
|
+
route này thành trình đọc file tuỳ ý. Path được resolve *bên trong* project rồi từ chối nếu
|
|
159
|
+
thoát ra (400) — cùng luật `extension-rpc-handlers.ts` đang dùng.
|
|
160
|
+
* **Quét APK trong project bị giới hạn**: sâu tối đa 8 cấp, prune `node_modules`/`.git`/`.gradle`…
|
|
161
|
+
Cùng lý do với lệnh cấm `fs.watch` đệ quy trong CLAUDE.md.
|
|
162
|
+
* **Hai hạn mức, không phải một.** `MAX_APK_BYTES` (2 GB) chặn một file vô lý; `MAX_STAGING_BYTES`
|
|
163
|
+
(4 GB) chặn nhiều file hợp lệ cùng lúc. Thiếu cái thứ hai thì hai upload 2 GB song song đều hợp
|
|
164
|
+
lệ theo hạn mức riêng và cùng nhau làm đầy đĩa mà database đang nằm trên đó.
|
|
165
|
+
|
|
166
|
+
## 9. Không log nội dung input/clipboard
|
|
167
|
+
|
|
168
|
+
Gate cuối. Kiểm tra bằng hai cách: `grep` mọi `console.*` trong `src/services/android/`,
|
|
169
|
+
`src/server/ws/android.ts`, `src/server/routes/android.ts` — ba chỗ duy nhất đều là thông báo lỗi
|
|
170
|
+
(`could not persist ownership`, `logcat stream … failed`, `failed to start a session`), không chỗ
|
|
171
|
+
nào chạm vào `text`/`entries`. Và grep chuỗi bí mật của e2e clipboard trong log server sau khi
|
|
172
|
+
chạy: **0 lần xuất hiện**.
|
|
173
|
+
|
|
174
|
+
## 10. Chưa đo — không được coi là đã đạt
|
|
175
|
+
|
|
176
|
+
* Split APK (`install-multiple`). Plan cho phép bỏ qua nếu chưa có workflow rõ ràng; hiện **không
|
|
177
|
+
hỗ trợ**, và một file `.apks`/`.aab` sẽ bị adb từ chối với `INSTALL_FAILED_INVALID_APK` — thông
|
|
178
|
+
báo có giải thích, nhưng vẫn là từ chối.
|
|
179
|
+
* Cài APK **lỗi ABI / minSdk / chữ ký thật**. Bảng `FAILURES` được unit test bằng output adb dựng
|
|
180
|
+
sẵn, chưa chạy với APK thật gây ra từng lỗi đó (cần APK arm-only và APK ký khác — chưa có).
|
|
181
|
+
* Upload qua tunnel Cloudflare (timeout idle của proxy với body 78 MB).
|
|
182
|
+
* Logcat trên máy **nói nhiều** (app đang crash-loop): mới đo ~40 msg/8 s trên máy nhàn rỗi.
|
|
183
|
+
* Panel trên trình duyệt mobile thật.
|
|
184
|
+
|
|
185
|
+
## 11. Tái lập
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
# 1. AVD thử nghiệm riêng, không đụng Pixel_9 / Pixel_Tablet của người dùng
|
|
189
|
+
bash spikes/android/boot-test-avd.sh &
|
|
190
|
+
AVD=ppm_spike_test2 LOG=/tmp/ppm-android-spike/emulator2.log bash spikes/android/boot-test-avd.sh &
|
|
191
|
+
|
|
192
|
+
# 2. E2E mức dịch vụ
|
|
193
|
+
ANDROID_EMULATOR_ENABLED=1 PPM_HOME=$(mktemp -d) bun tests/e2e/android-phase3-e2e.ts
|
|
194
|
+
|
|
195
|
+
# 3. Gate hai máy
|
|
196
|
+
ANDROID_EMULATOR_ENABLED=1 PPM_HOME=$(mktemp -d) bun tests/e2e/android-two-device-install-e2e.ts
|
|
197
|
+
|
|
198
|
+
# 4. E2E qua server thật (PPM_SERVER_HOME để check staging đúng PPM_HOME của server)
|
|
199
|
+
SRV=$(mktemp -d)
|
|
200
|
+
ANDROID_EMULATOR_ENABLED=1 PPM_HOME=$SRV bun src/server/index.ts __serve__ 8099 127.0.0.1 &
|
|
201
|
+
PPM_SERVER_HOME=$SRV PPM_BASE=http://127.0.0.1:8099 PPM_TOKEN=<token> \
|
|
202
|
+
PPM_HOME=$(mktemp -d) bun tests/e2e/android-ws-e2e.ts
|
|
203
|
+
```
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# Phase 4 report — Tạo, wipe, xoá AVD ngay trong PPM
|
|
2
|
+
|
|
3
|
+
Ngày: 2026-09-21. Tiếp sau `android-emulator-phase3-report-260921.md`.
|
|
4
|
+
|
|
5
|
+
Bản này ghi **những gì đo được**. Trong Phase 4 có ba lỗi đáng kể, và cả ba đều **im lặng**:
|
|
6
|
+
một lỗi ghi AVD vào thư mục AVD **thật của người dùng**, một lỗi khiến mọi emulator đang chạy
|
|
7
|
+
đều bị báo là "không ai khoá", và một lỗi của chính bài test chứ không phải của sản phẩm.
|
|
8
|
+
|
|
9
|
+
## 1. Kết quả
|
|
10
|
+
|
|
11
|
+
| | |
|
|
12
|
+
|---|---|
|
|
13
|
+
| E2E gate Phase 4 (`tests/e2e/android-avd-crud-e2e.ts`) | **34/34** — tạo → boot → ghi dữ liệu → stop → boot lại → wipe → boot lại → xoá |
|
|
14
|
+
| E2E qua server thật (`tests/e2e/android-ws-e2e.ts`) | **48/48** — 34 check cũ + **14 check mới** cho route Phase 4 |
|
|
15
|
+
| Unit mới | **34** (avd-manager 20, system-images 13, lock NUL 1) — cộng dồn **143** test Android trên 15 file |
|
|
16
|
+
| `tsc --noEmit` | 0 lỗi |
|
|
17
|
+
| `bun run build:web` | chunk `android-tab-*.js` **48.35 kB** (gzip 14.08) — tăng ~11 kB so với Phase 3, vẫn lazy |
|
|
18
|
+
| Toàn bộ `bun test` | 7146 pass / 33 fail — **không có test Android nào đỏ**; 33 lỗi nằm trong các nhóm đã biết từ trước (codex 18, tunnel 7, cloud-ws 2, 6 lỗi lẻ), số lượng dao động giữa các lần chạy ở nhóm tunnel |
|
|
19
|
+
|
|
20
|
+
Số đo có ý nghĩa:
|
|
21
|
+
|
|
22
|
+
- `avdmanager list device` mất **~730 ms** lần đầu và **0 ms** sau đó (cache trong tiến trình);
|
|
23
|
+
88 profile, trong đó 39 phone và 13 tablet.
|
|
24
|
+
- Đọc system image từ **đĩa** thay vì `sdkmanager --list_installed`: tức thì, không có thanh
|
|
25
|
+
tiến trình phải bóc tách, và vẫn trả lời đúng trên máy có image nhưng **không có** cmdline-tools.
|
|
26
|
+
- AVD tạo bởi PPM: boot lần đầu **25 s**, boot lại từ snapshot **3 s**, boot sau wipe **24 s**.
|
|
27
|
+
- Một wipe giải phóng **3.2 GB** trên AVD đã boot hai lần.
|
|
28
|
+
|
|
29
|
+
## 2. `ANDROID_AVD_HOME` trỏ vào thư mục **không tồn tại** thì avdmanager ghi vào `~/.android/avd`
|
|
30
|
+
|
|
31
|
+
Đây là biến thể thứ hai của cùng một cái bẫy, và nó nguy hiểm hơn cái đầu.
|
|
32
|
+
|
|
33
|
+
- Lần đầu (đã ghi ở Phase 4 khi đang code): **không** set `ANDROID_AVD_HOME` cho tiến trình con
|
|
34
|
+
thì `avdmanager` ghi AVD vào `~/.android/avd` — đúng thư mục AVD thật của người dùng. Sửa bằng
|
|
35
|
+
`avdHomeEnv()` trong `sdk-discovery.ts`, dùng chung với `emulator-launcher.ts`.
|
|
36
|
+
- Lần hai: **có** set `ANDROID_AVD_HOME`, nhưng trỏ vào thư mục vừa bị xoá. `avdmanager` **không
|
|
37
|
+
báo lỗi** — nó lặng lẽ quay về `~/.android/avd` và ghi AVD ở đó. Phát hiện bằng
|
|
38
|
+
`find ~ -maxdepth 4 -name 'ppm_wipe_probe*'`.
|
|
39
|
+
|
|
40
|
+
Cả hai lần đều không đụng `Pixel_9` / `Pixel_Tablet`; AVD lạc đã được xoá tay ngay.
|
|
41
|
+
|
|
42
|
+
Sửa: một dòng `mkdirSync(opts.avdHome, { recursive: true })` trước khi chạy tool. Kiểm tra
|
|
43
|
+
"config.ini có xuất hiện đúng chỗ không" ở cuối `createAvd` vẫn giữ nguyên — nó chính là thứ đã
|
|
44
|
+
**bắt được** cả hai lần, nên nó không phải kiểm tra thừa.
|
|
45
|
+
|
|
46
|
+
Bài học chung: một biến môi trường kiểu "thay thế đường dẫn" cần thư mục **có thật**; nếu không,
|
|
47
|
+
tool sẽ dùng mặc định và trông như đang hoạt động bình thường.
|
|
48
|
+
|
|
49
|
+
## 3. File lock của emulator kết thúc bằng NUL, nên `trim()` không cứu được
|
|
50
|
+
|
|
51
|
+
`hardware-qemu.ini.lock` của một emulator đang chạy, đọc bằng `xxd`:
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
00000000: 3531 3937 3732 00 519772.
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
7 byte: `519772` rồi **một byte NUL**. `String.prototype.trim()` chỉ bỏ khoảng trắng, mà NUL
|
|
58
|
+
không phải khoảng trắng — nên `Number("519772\0")` là `NaN`, `Number.isInteger(NaN)` là `false`,
|
|
59
|
+
và `isLocked()` trả `false` cho **mọi** emulator đang chạy.
|
|
60
|
+
|
|
61
|
+
Hậu quả, tất cả đều im lặng:
|
|
62
|
+
|
|
63
|
+
- Dòng cảnh báo "Locked by another process — it is probably open in Android Studio" trong danh
|
|
64
|
+
sách thiết bị **chưa từng hiện**.
|
|
65
|
+
- `assertDestructible()` của Phase 4 có một lớp bảo vệ không hoạt động (lớp "đang chạy" vẫn
|
|
66
|
+
hoạt động, vì nó quét tiến trình chứ không đọc lock).
|
|
67
|
+
|
|
68
|
+
Lý do nó sống sót qua Phase 1: unit test tự viết fixture `` `${process.pid} ` `` — pid kèm một
|
|
69
|
+
**dấu cách**. `trim()` xử lý được dấu cách, nên bài test xanh với đúng giả định sai của người
|
|
70
|
+
viết nó. Bài test mới ghi đúng thứ emulator ghi (`${pid}\0`) và đỏ trước khi sửa.
|
|
71
|
+
|
|
72
|
+
Sửa: lấy cụm số ở đầu file (`/^\s*(\d+)/`) thay vì tin vào `trim()`.
|
|
73
|
+
|
|
74
|
+
Kiểm tra lại trên máy thật, có một emulator đang chạy:
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
ppm_spike_test locked=true ← đang chạy
|
|
78
|
+
ppm_spike_test2 locked=false
|
|
79
|
+
Pixel_9 locked=false
|
|
80
|
+
Pixel_Tablet locked=false
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## 4. `userdata.img` **không** được xoá khi wipe — tài liệu của chính emulator nói vậy
|
|
84
|
+
|
|
85
|
+
Danh sách wipe ban đầu có `userdata.img`. `emulator -help-disk-images` xếp nó vào nhóm ảnh
|
|
86
|
+
**khởi tạo**, cùng chỗ với `system.img`, và định nghĩa wipe là:
|
|
87
|
+
|
|
88
|
+
> `-wipe-data` Copy the content of the *initial* user data image (userdata.img) into the
|
|
89
|
+
> writable one (userdata-qemu.img)
|
|
90
|
+
|
|
91
|
+
Tức là xoá `userdata.img` chính là xoá bản gốc mà một lần wipe cần để khôi phục.
|
|
92
|
+
|
|
93
|
+
Đo thêm cho đủ: AVD tạo bằng tooling hiện tại trên máy này **không có** `userdata.img` trong thư
|
|
94
|
+
mục AVD (nó nằm trong thư mục system image), nên lỗi này không biểu hiện ở đây — boot sau wipe
|
|
95
|
+
vẫn 24 s. Nó chỉ cắn một AVD do tooling cũ tạo ra, im lặng, ở lần boot kế tiếp. Đúng loại việc
|
|
96
|
+
mà danh sách cho phép (allowlist) sinh ra để tránh.
|
|
97
|
+
|
|
98
|
+
## 5. Ba lỗi của **bài test**, không phải của sản phẩm
|
|
99
|
+
|
|
100
|
+
1. **`adb shell sh -c "echo X > file"`** — `adb` nối các tham số bằng dấu cách rồi đưa cả chuỗi
|
|
101
|
+
cho shell **của máy ảo**, nên dấu `>` bị shell đó nuốt: `sh -c echo` chạy với `echo` là lệnh và
|
|
102
|
+
ghi một dòng trống. File có thật, nội dung rỗng, và hai check "ghi được file" + "dữ liệu còn
|
|
103
|
+
sau khi stop" cùng đỏ. Sửa: truyền **một** tham số duy nhất.
|
|
104
|
+
2. **`boot()` nuốt output của emulator** — một lần boot không xong chỉ báo đúng chữ `no`, tức là
|
|
105
|
+
thứ duy nhất đáng quan tâm thì không có. Giờ mỗi lần boot ghi ra một file log và phần tổng kết
|
|
106
|
+
in đường dẫn của những lần boot hỏng.
|
|
107
|
+
3. **Một lần "boot sau wipe" hỏng không tái lập được** — chạy lại đúng kịch bản đó bằng một
|
|
108
|
+
probe riêng (tạo → boot → ghi → stop → wipe → boot) thì boot lần hai mất 24 s và thành công;
|
|
109
|
+
lần chạy e2e kế tiếp cũng qua. Ghi lại ở đây thay vì tuyên bố là đã hiểu: nó **chưa được giải
|
|
110
|
+
thích**, chỉ là không lặp lại.
|
|
111
|
+
|
|
112
|
+
## 6. Quyết định thiết kế
|
|
113
|
+
|
|
114
|
+
- **PPM không tải system image.** `/system-images` đọc cây `system-images/<api>/<tag>/<abi>/
|
|
115
|
+
source.properties` trên đĩa. Không có nút tải, không tự chấp nhận licence — đúng plan Phase 4.
|
|
116
|
+
Image sai CPU vẫn được **liệt kê** nhưng không chọn được, kèm lý do ngay trên dòng đó: ẩn đi thì
|
|
117
|
+
người dùng biết mình đã cài mà không thấy, và sẽ nghĩ là lỗi.
|
|
118
|
+
- **Bắt buộc có device profile.** `avdmanager create avd` không `--device` cho ra AVD **320x640**
|
|
119
|
+
(đã đo ở Phase 4 khi code), trông như emulator hỏng chứ không như thiếu tham số.
|
|
120
|
+
- **`hw.keyboard=yes` luôn được ghi** sau khi tạo. `avdmanager` ghi `no`; với `no` thì `sendKey`
|
|
121
|
+
trả OK và không một ký tự nào tới máy ảo (đo ở Phase 0).
|
|
122
|
+
- **Không `--force`.** Trùng tên bị từ chối, vì ghi đè một AVD là hành vi phá huỷ đội lốt tạo mới.
|
|
123
|
+
- **Tên được kiểm tra, không được escape.** Regex nằm ở `src/shared/android-avd.ts` và **dùng
|
|
124
|
+
chung** cho form lẫn host — hai bản sao của một regex kiểm tra là hai bản sẽ lệch nhau, và cách
|
|
125
|
+
chúng lệch là form nhận một cái tên rồi host từ chối sau khi hộp thoại đã đóng.
|
|
126
|
+
- **Wipe là allowlist chứ không phải glob.** File nào code này chưa biết thì **giữ lại**.
|
|
127
|
+
- **Wipe/Delete: gõ lại đúng tên máy.** Không phải checkbox — các dòng trong danh sách nhìn giống
|
|
128
|
+
hệt nhau, và checkbox chỉ xác nhận rằng có một hộp thoại đang mở, không xác nhận nó mở trên máy nào.
|
|
129
|
+
- **Nút ba chấm nhìn thấy được**, không phải right-click hay long-press: wipe và delete không có
|
|
130
|
+
đường nào khác, mà một cử chỉ ẩn thì không phải là một đường đi. Nó là control riêng nên không
|
|
131
|
+
cướp mất cú chạm của cả dòng (bẫy đã ghi trong CLAUDE.md).
|
|
132
|
+
|
|
133
|
+
## 7. UI đo trên trình duyệt thật, không chỉ đọc code
|
|
134
|
+
|
|
135
|
+
Chạy PPM đã build (`bun run build:web` rồi serve `dist/web`) và lái bằng Playwright, ở **390x844**:
|
|
136
|
+
|
|
137
|
+
| | |
|
|
138
|
+
|---|---|
|
|
139
|
+
| Hộp thoại tạo máy dưới `md` | là **bottom sheet** thật (đáy dính đáy màn hình) |
|
|
140
|
+
| Tràn ngang | **không** (`scrollWidth === innerWidth`) |
|
|
141
|
+
| Vùng chạm | 7/7 control **44 px** — nhưng chỉ sau khi sửa: trigger của `SearchSelect` mặc định là `h-10 md:h-7`, tức **40 px**, dưới ngưỡng. Đo mới thấy, đọc code thì không. |
|
|
142
|
+
| Nút ba chấm mỗi dòng | 44x44 |
|
|
143
|
+
| Mục menu | "Wipe data" 44 px, "Delete device" 44 px và `data-variant="destructive"` |
|
|
144
|
+
| Sheet xác nhận | nút vẫn **disabled** khi gõ tên sai (`Pixel_9_wrong`) và khi thiếu **một ký tự** (`ppm_ui_prob`) |
|
|
145
|
+
|
|
146
|
+
Và chạy trọn vòng **qua chính UI** với server thật: tạo `ppm_ui_probe` → nó hiện trong danh sách
|
|
147
|
+
(API 37, 1080x2424, **không** có cảnh báo thiếu hardware keyboard, tức `hw.keyboard=yes` đã vào)
|
|
148
|
+
→ xoá bằng cách gõ đúng tên → biến mất. `Pixel_9` (26 file) và `Pixel_Tablet` (22 file) nguyên vẹn.
|
|
149
|
+
|
|
150
|
+
Một quan sát phụ, tình cờ: `vite build` xoá `dist/web` nên tab đang mở mất chunk — và màn hình
|
|
151
|
+
khôi phục chunk (`root-error-boundary` + watchdog) đã bắt đúng, hiện nút Reload thay vì trắng trang.
|
|
152
|
+
|
|
153
|
+
## 8. Cách viết test phá huỷ mà không phá gì
|
|
154
|
+
|
|
155
|
+
Trong `android-ws-e2e.ts` (chạy với AVD **thật** của người dùng) có check "không wipe được máy
|
|
156
|
+
đang chạy". Nó gửi kèm `confirmName` **sai**:
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
{ confirmName: "definitely-not-its-name" }
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
rồi mới khẳng định thông báo lỗi nói về *đang chạy*. Nghĩa là phải **hai** lớp bảo vệ cùng hỏng
|
|
163
|
+
mới mất dữ liệu. Một bài test có thể phá dữ liệu người dùng khi code hỏng thì không đáng viết.
|
|
164
|
+
|
|
165
|
+
Còn gate thật của Phase 4 (tạo/wipe/xoá thật) nằm ở `android-avd-crud-e2e.ts`, chạy trong AVD
|
|
166
|
+
home riêng `~/.android-phase4-avd` và tự dọn — không đụng `Pixel_9` / `Pixel_Tablet` (plan §9).
|
|
167
|
+
|
|
168
|
+
## 9. Chưa đo — không được coi là đã đạt
|
|
169
|
+
|
|
170
|
+
- Chưa thử trên Windows / macOS: `avdmanager` là `.bat` trên Windows và cách nó nhận stdin `no\n`
|
|
171
|
+
chưa được kiểm tra ở đó.
|
|
172
|
+
- Chưa thử với AVD do **Android Studio** đang mở (lock thật của Studio, chứ không phải của
|
|
173
|
+
emulator do PPM khởi động) — giờ mới có `isLocked` đúng để thử.
|
|
174
|
+
- Chưa thử tạo AVD khi ổ đĩa đầy, và chưa thử `sdCardMb: 0` trên máy thật.
|
|
175
|
+
- Chưa đo p50/p95 click-to-visible trên LAN và qua tunnel (vẫn là mục còn nợ từ §10 của plan).
|
|
176
|
+
|
|
177
|
+
## 10. Tái lập
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
# Gate Phase 4 — mất vài phút, 4 lần boot, AVD home riêng
|
|
181
|
+
ANDROID_EMULATOR_ENABLED=1 PPM_E2E_LOG_DIR=/tmp/p4logs PPM_HOME=$(mktemp -d) \
|
|
182
|
+
bun tests/e2e/android-avd-crud-e2e.ts
|
|
183
|
+
|
|
184
|
+
# Route Phase 4 qua server thật
|
|
185
|
+
ANDROID_EMULATOR_ENABLED=1 bun src/server/index.ts __serve__ 8099 127.0.0.1 dev
|
|
186
|
+
PPM_BASE=http://127.0.0.1:8099 PPM_TOKEN=<token> PPM_HOME=$(mktemp -d) \
|
|
187
|
+
bun tests/e2e/android-ws-e2e.ts
|
|
188
|
+
|
|
189
|
+
# Unit
|
|
190
|
+
bun test tests/unit/android tests/unit/web/android-*.test.ts
|
|
191
|
+
```
|