@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
package/dist/web/sw.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
try{self[`workbox:core:7.3.0`]&&_()}catch{}var e=(e,...t)=>{let n=e;return t.length>0&&(n+=` :: ${JSON.stringify(t)}`),n},t=class extends Error{constructor(t,n){let r=e(t,n);super(r),this.name=t,this.details=n}},n={googleAnalytics:`googleAnalytics`,precache:`precache-v2`,prefix:`workbox`,runtime:`runtime`,suffix:typeof registration<`u`?registration.scope:``},r=e=>[n.prefix,e,n.suffix].filter(e=>e&&e.length>0).join(`-`),i=e=>{for(let t of Object.keys(n))e(t)},a={updateDetails:e=>{i(t=>{typeof e[t]==`string`&&(n[t]=e[t])})},getGoogleAnalyticsName:e=>e||r(n.googleAnalytics),getPrecacheName:e=>e||r(n.precache),getPrefix:()=>n.prefix,getRuntimeName:e=>e||r(n.runtime),getSuffix:()=>n.suffix};function o(e,t){let n=t();return e.waitUntil(n),n}try{self[`workbox:precaching:7.3.0`]&&_()}catch{}var s=`__WB_REVISION__`;function c(e){if(!e)throw new t(`add-to-cache-list-unexpected-type`,{entry:e});if(typeof e==`string`){let t=new URL(e,location.href);return{cacheKey:t.href,url:t.href}}let{revision:n,url:r}=e;if(!r)throw new t(`add-to-cache-list-unexpected-type`,{entry:e});if(!n){let e=new URL(r,location.href);return{cacheKey:e.href,url:e.href}}let i=new URL(r,location.href),a=new URL(r,location.href);return i.searchParams.set(s,n),{cacheKey:i.href,url:a.href}}var l=class{constructor(){this.updatedURLs=[],this.notUpdatedURLs=[],this.handlerWillStart=async({request:e,state:t})=>{t&&(t.originalRequest=e)},this.cachedResponseWillBeUsed=async({event:e,state:t,cachedResponse:n})=>{if(e.type===`install`&&t&&t.originalRequest&&t.originalRequest instanceof Request){let e=t.originalRequest.url;n?this.notUpdatedURLs.push(e):this.updatedURLs.push(e)}return n}}},u=class{constructor({precacheController:e}){this.cacheKeyWillBeUsed=async({request:e,params:t})=>{let n=t?.cacheKey||this._precacheController.getCacheKeyForURL(e.url);return n?new Request(n,{headers:e.headers}):e},this._precacheController=e}},d;function f(){if(d===void 0){let e=new Response(``);if(`body`in e)try{new Response(e.body),d=!0}catch{d=!1}d=!1}return d}async function p(e,n){let r=null;if(e.url&&(r=new URL(e.url).origin),r!==self.location.origin)throw new t(`cross-origin-copy-response`,{origin:r});let i=e.clone(),a={headers:new Headers(i.headers),status:i.status,statusText:i.statusText},o=n?n(a):a,s=f()?i.body:await i.blob();return new Response(s,o)}var m=e=>new URL(String(e),location.href).href.replace(RegExp(`^${location.origin}`),``);function h(e,t){let n=new URL(e);for(let e of t)n.searchParams.delete(e);return n.href}async function g(e,t,n,r){let i=h(t.url,n);if(t.url===i)return e.match(t,r);let a=Object.assign(Object.assign({},r),{ignoreSearch:!0}),o=await e.keys(t,a);for(let t of o)if(i===h(t.url,n))return e.match(t,r)}var v=class{constructor(){this.promise=new Promise((e,t)=>{this.resolve=e,this.reject=t})}},y=new Set;async function b(){for(let e of y)await e()}function x(e){return new Promise(t=>setTimeout(t,e))}try{self[`workbox:strategies:7.3.0`]&&_()}catch{}function S(e){return typeof e==`string`?new Request(e):e}var C=class{constructor(e,t){this._cacheKeys={},Object.assign(this,t),this.event=t.event,this._strategy=e,this._handlerDeferred=new v,this._extendLifetimePromises=[],this._plugins=[...e.plugins],this._pluginStateMap=new Map;for(let e of this._plugins)this._pluginStateMap.set(e,{});this.event.waitUntil(this._handlerDeferred.promise)}async fetch(e){let{event:n}=this,r=S(e);if(r.mode===`navigate`&&n instanceof FetchEvent&&n.preloadResponse){let e=await n.preloadResponse;if(e)return e}let i=this.hasCallback(`fetchDidFail`)?r.clone():null;try{for(let e of this.iterateCallbacks(`requestWillFetch`))r=await e({request:r.clone(),event:n})}catch(e){if(e instanceof Error)throw new t(`plugin-error-request-will-fetch`,{thrownErrorMessage:e.message})}let a=r.clone();try{let e;e=await fetch(r,r.mode===`navigate`?void 0:this._strategy.fetchOptions);for(let t of this.iterateCallbacks(`fetchDidSucceed`))e=await t({event:n,request:a,response:e});return e}catch(e){throw i&&await this.runCallbacks(`fetchDidFail`,{error:e,event:n,originalRequest:i.clone(),request:a.clone()}),e}}async fetchAndCachePut(e){let t=await this.fetch(e),n=t.clone();return this.waitUntil(this.cachePut(e,n)),t}async cacheMatch(e){let t=S(e),n,{cacheName:r,matchOptions:i}=this._strategy,a=await this.getCacheKey(t,`read`),o=Object.assign(Object.assign({},i),{cacheName:r});n=await caches.match(a,o);for(let e of this.iterateCallbacks(`cachedResponseWillBeUsed`))n=await e({cacheName:r,matchOptions:i,cachedResponse:n,request:a,event:this.event})||void 0;return n}async cachePut(e,n){let r=S(e);await x(0);let i=await this.getCacheKey(r,`write`);if(!n)throw new t(`cache-put-with-no-response`,{url:m(i.url)});let a=await this._ensureResponseSafeToCache(n);if(!a)return!1;let{cacheName:o,matchOptions:s}=this._strategy,c=await self.caches.open(o),l=this.hasCallback(`cacheDidUpdate`),u=l?await g(c,i.clone(),[`__WB_REVISION__`],s):null;try{await c.put(i,l?a.clone():a)}catch(e){if(e instanceof Error)throw e.name===`QuotaExceededError`&&await b(),e}for(let e of this.iterateCallbacks(`cacheDidUpdate`))await e({cacheName:o,oldResponse:u,newResponse:a.clone(),request:i,event:this.event});return!0}async getCacheKey(e,t){let n=`${e.url} | ${t}`;if(!this._cacheKeys[n]){let r=e;for(let e of this.iterateCallbacks(`cacheKeyWillBeUsed`))r=S(await e({mode:t,request:r,event:this.event,params:this.params}));this._cacheKeys[n]=r}return this._cacheKeys[n]}hasCallback(e){for(let t of this._strategy.plugins)if(e in t)return!0;return!1}async runCallbacks(e,t){for(let n of this.iterateCallbacks(e))await n(t)}*iterateCallbacks(e){for(let t of this._strategy.plugins)if(typeof t[e]==`function`){let n=this._pluginStateMap.get(t);yield r=>{let i=Object.assign(Object.assign({},r),{state:n});return t[e](i)}}}waitUntil(e){return this._extendLifetimePromises.push(e),e}async doneWaiting(){for(;this._extendLifetimePromises.length;){let e=this._extendLifetimePromises.splice(0),t=(await Promise.allSettled(e)).find(e=>e.status===`rejected`);if(t)throw t.reason}}destroy(){this._handlerDeferred.resolve(null)}async _ensureResponseSafeToCache(e){let t=e,n=!1;for(let e of this.iterateCallbacks(`cacheWillUpdate`))if(t=await e({request:this.request,response:t,event:this.event})||void 0,n=!0,!t)break;return n||t&&t.status!==200&&(t=void 0),t}},w=class{constructor(e={}){this.cacheName=a.getRuntimeName(e.cacheName),this.plugins=e.plugins||[],this.fetchOptions=e.fetchOptions,this.matchOptions=e.matchOptions}handle(e){let[t]=this.handleAll(e);return t}handleAll(e){e instanceof FetchEvent&&(e={event:e,request:e.request});let t=e.event,n=typeof e.request==`string`?new Request(e.request):e.request,r=`params`in e?e.params:void 0,i=new C(this,{event:t,request:n,params:r}),a=this._getResponse(i,n,t);return[a,this._awaitComplete(a,i,n,t)]}async _getResponse(e,n,r){await e.runCallbacks(`handlerWillStart`,{event:r,request:n});let i;try{if(i=await this._handle(n,e),!i||i.type===`error`)throw new t(`no-response`,{url:n.url})}catch(t){if(t instanceof Error){for(let a of e.iterateCallbacks(`handlerDidError`))if(i=await a({error:t,event:r,request:n}),i)break}if(!i)throw t}for(let t of e.iterateCallbacks(`handlerWillRespond`))i=await t({event:r,request:n,response:i});return i}async _awaitComplete(e,t,n,r){let i,a;try{i=await e}catch{}try{await t.runCallbacks(`handlerDidRespond`,{event:r,request:n,response:i}),await t.doneWaiting()}catch(e){e instanceof Error&&(a=e)}if(await t.runCallbacks(`handlerDidComplete`,{event:r,request:n,response:i,error:a}),t.destroy(),a)throw a}},T=class e extends w{constructor(t={}){t.cacheName=a.getPrecacheName(t.cacheName),super(t),this._fallbackToNetwork=t.fallbackToNetwork!==!1,this.plugins.push(e.copyRedirectedCacheableResponsesPlugin)}async _handle(e,t){return await t.cacheMatch(e)||(t.event&&t.event.type===`install`?await this._handleInstall(e,t):await this._handleFetch(e,t))}async _handleFetch(e,n){let r,i=n.params||{};if(this._fallbackToNetwork){let t=i.integrity,a=e.integrity,o=!a||a===t;r=await n.fetch(new Request(e,{integrity:e.mode===`no-cors`?void 0:a||t})),t&&o&&e.mode!==`no-cors`&&(this._useDefaultCacheabilityPluginIfNeeded(),await n.cachePut(e,r.clone()))}else throw new t(`missing-precache-entry`,{cacheName:this.cacheName,url:e.url});return r}async _handleInstall(e,n){this._useDefaultCacheabilityPluginIfNeeded();let r=await n.fetch(e);if(!await n.cachePut(e,r.clone()))throw new t(`bad-precaching-response`,{url:e.url,status:r.status});return r}_useDefaultCacheabilityPluginIfNeeded(){let t=null,n=0;for(let[r,i]of this.plugins.entries())i!==e.copyRedirectedCacheableResponsesPlugin&&(i===e.defaultPrecacheCacheabilityPlugin&&(t=r),i.cacheWillUpdate&&n++);n===0?this.plugins.push(e.defaultPrecacheCacheabilityPlugin):n>1&&t!==null&&this.plugins.splice(t,1)}};T.defaultPrecacheCacheabilityPlugin={async cacheWillUpdate({response:e}){return!e||e.status>=400?null:e}},T.copyRedirectedCacheableResponsesPlugin={async cacheWillUpdate({response:e}){return e.redirected?await p(e):e}};var E=class{constructor({cacheName:e,plugins:t=[],fallbackToNetwork:n=!0}={}){this._urlsToCacheKeys=new Map,this._urlsToCacheModes=new Map,this._cacheKeysToIntegrities=new Map,this._strategy=new T({cacheName:a.getPrecacheName(e),plugins:[...t,new u({precacheController:this})],fallbackToNetwork:n}),this.install=this.install.bind(this),this.activate=this.activate.bind(this)}get strategy(){return this._strategy}precache(e){this.addToCacheList(e),this._installAndActiveListenersAdded||=(self.addEventListener(`install`,this.install),self.addEventListener(`activate`,this.activate),!0)}addToCacheList(e){let n=[];for(let r of e){typeof r==`string`?n.push(r):r&&r.revision===void 0&&n.push(r.url);let{cacheKey:e,url:i}=c(r),a=typeof r!=`string`&&r.revision?`reload`:`default`;if(this._urlsToCacheKeys.has(i)&&this._urlsToCacheKeys.get(i)!==e)throw new t(`add-to-cache-list-conflicting-entries`,{firstEntry:this._urlsToCacheKeys.get(i),secondEntry:e});if(typeof r!=`string`&&r.integrity){if(this._cacheKeysToIntegrities.has(e)&&this._cacheKeysToIntegrities.get(e)!==r.integrity)throw new t(`add-to-cache-list-conflicting-integrities`,{url:i});this._cacheKeysToIntegrities.set(e,r.integrity)}if(this._urlsToCacheKeys.set(i,e),this._urlsToCacheModes.set(i,a),n.length>0){let e=`Workbox is precaching URLs without revision info: ${n.join(`, `)}\nThis is generally NOT safe. Learn more at https://bit.ly/wb-precache`;console.warn(e)}}}install(e){return o(e,async()=>{let t=new l;this.strategy.plugins.push(t);for(let[t,n]of this._urlsToCacheKeys){let r=this._cacheKeysToIntegrities.get(n),i=this._urlsToCacheModes.get(t),a=new Request(t,{integrity:r,cache:i,credentials:`same-origin`});await Promise.all(this.strategy.handleAll({params:{cacheKey:n},request:a,event:e}))}let{updatedURLs:n,notUpdatedURLs:r}=t;return{updatedURLs:n,notUpdatedURLs:r}})}activate(e){return o(e,async()=>{let e=await self.caches.open(this.strategy.cacheName),t=await e.keys(),n=new Set(this._urlsToCacheKeys.values()),r=[];for(let i of t)n.has(i.url)||(await e.delete(i),r.push(i.url));return{deletedURLs:r}})}getURLsToCacheKeys(){return this._urlsToCacheKeys}getCachedURLs(){return[...this._urlsToCacheKeys.keys()]}getCacheKeyForURL(e){let t=new URL(e,location.href);return this._urlsToCacheKeys.get(t.href)}getIntegrityForCacheKey(e){return this._cacheKeysToIntegrities.get(e)}async matchPrecache(e){let t=e instanceof Request?e.url:e,n=this.getCacheKeyForURL(t);if(n)return(await self.caches.open(this.strategy.cacheName)).match(n)}createHandlerBoundToURL(e){let n=this.getCacheKeyForURL(e);if(!n)throw new t(`non-precached-url`,{url:e});return t=>(t.request=new Request(e),t.params=Object.assign({cacheKey:n},t.params),this.strategy.handle(t))}},D,O=()=>(D||=new E,D);try{self[`workbox:routing:7.3.0`]&&_()}catch{}var k=e=>e&&typeof e==`object`?e:{handle:e},A=class{constructor(e,t,n=`GET`){this.handler=k(t),this.match=e,this.method=n}setCatchHandler(e){this.catchHandler=k(e)}},j=class extends A{constructor(e,t,n){super(({url:t})=>{let n=e.exec(t.href);if(n&&!(t.origin!==location.origin&&n.index!==0))return n.slice(1)},t,n)}},M=class{constructor(){this._routes=new Map,this._defaultHandlerMap=new Map}get routes(){return this._routes}addFetchListener(){self.addEventListener(`fetch`,(e=>{let{request:t}=e,n=this.handleRequest({request:t,event:e});n&&e.respondWith(n)}))}addCacheListener(){self.addEventListener(`message`,(e=>{if(e.data&&e.data.type===`CACHE_URLS`){let{payload:t}=e.data,n=Promise.all(t.urlsToCache.map(t=>{typeof t==`string`&&(t=[t]);let n=new Request(...t);return this.handleRequest({request:n,event:e})}));e.waitUntil(n),e.ports&&e.ports[0]&&n.then(()=>e.ports[0].postMessage(!0))}}))}handleRequest({request:e,event:t}){let n=new URL(e.url,location.href);if(!n.protocol.startsWith(`http`))return;let r=n.origin===location.origin,{params:i,route:a}=this.findMatchingRoute({event:t,request:e,sameOrigin:r,url:n}),o=a&&a.handler,s=e.method;if(!o&&this._defaultHandlerMap.has(s)&&(o=this._defaultHandlerMap.get(s)),!o)return;let c;try{c=o.handle({url:n,request:e,event:t,params:i})}catch(e){c=Promise.reject(e)}let l=a&&a.catchHandler;return c instanceof Promise&&(this._catchHandler||l)&&(c=c.catch(async r=>{if(l)try{return await l.handle({url:n,request:e,event:t,params:i})}catch(e){e instanceof Error&&(r=e)}if(this._catchHandler)return this._catchHandler.handle({url:n,request:e,event:t});throw r})),c}findMatchingRoute({url:e,sameOrigin:t,request:n,event:r}){let i=this._routes.get(n.method)||[];for(let a of i){let i,o=a.match({url:e,sameOrigin:t,request:n,event:r});if(o)return i=o,(Array.isArray(i)&&i.length===0||o.constructor===Object&&Object.keys(o).length===0||typeof o==`boolean`)&&(i=void 0),{route:a,params:i}}return{}}setDefaultHandler(e,t=`GET`){this._defaultHandlerMap.set(t,k(e))}setCatchHandler(e){this._catchHandler=k(e)}registerRoute(e){this._routes.has(e.method)||this._routes.set(e.method,[]),this._routes.get(e.method).push(e)}unregisterRoute(e){if(!this._routes.has(e.method))throw new t(`unregister-route-but-not-found-with-method`,{method:e.method});let n=this._routes.get(e.method).indexOf(e);if(n>-1)this._routes.get(e.method).splice(n,1);else throw new t(`unregister-route-route-not-registered`)}},N,P=()=>(N||(N=new M,N.addFetchListener(),N.addCacheListener()),N);function F(e,n,r){let i;if(typeof e==`string`){let t=new URL(e,location.href);i=new A(({url:e})=>e.href===t.href,n,r)}else if(e instanceof RegExp)i=new j(e,n,r);else if(typeof e==`function`)i=new A(e,n,r);else if(e instanceof A)i=e;else throw new t(`unsupported-route-type`,{moduleName:`workbox-routing`,funcName:`registerRoute`,paramName:`capture`});return P().registerRoute(i),i}function I(e,t=[]){for(let n of[...e.searchParams.keys()])t.some(e=>e.test(n))&&e.searchParams.delete(n);return e}function*L(e,{ignoreURLParametersMatching:t=[/^utm_/,/^fbclid$/],directoryIndex:n=`index.html`,cleanURLs:r=!0,urlManipulation:i}={}){let a=new URL(e,location.href);a.hash=``,yield a.href;let o=I(a,t);if(yield o.href,n&&o.pathname.endsWith(`/`)){let e=new URL(o.href);e.pathname+=n,yield e.href}if(r){let e=new URL(o.href);e.pathname+=`.html`,yield e.href}if(i){let e=i({url:a});for(let t of e)yield t.href}}var R=class extends A{constructor(e,t){super(({request:n})=>{let r=e.getURLsToCacheKeys();for(let i of L(n.url,t)){let t=r.get(i);if(t)return{cacheKey:t,integrity:e.getIntegrityForCacheKey(t)}}},e.strategy)}};function z(e){F(new R(O(),e))}var B=`-precache-`,V=async(e,t=B)=>{let n=(await self.caches.keys()).filter(n=>n.includes(t)&&n.includes(self.registration.scope)&&n!==e);return await Promise.all(n.map(e=>self.caches.delete(e))),n};function H(){self.addEventListener(`activate`,(e=>{let t=a.getPrecacheName();e.waitUntil(V(t).then(e=>{}))}))}function U(e){O().precache(e)}function W(e,t){U(e),z(t)}var G=class extends w{async _handle(e,n){let r=await n.cacheMatch(e),i;if(!r)try{r=await n.fetchAndCachePut(e)}catch(e){e instanceof Error&&(i=e)}if(!r)throw new t(`no-response`,{url:e.url,error:i});return r}};W([{"revision":"207d5c365d8592b728c5fbbfaebe9a60","url":"index.html"},{"revision":"26dccd02a2ef7522892015154f5e3680","url":"manifest.webmanifest"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-512.svg"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-192.svg"},{"revision":null,"url":"assets/index-oIvYSKTT.css"},{"revision":null,"url":"assets/index-DhWjx5iR.js"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-192.svg"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-512.svg"},{"revision":"26dccd02a2ef7522892015154f5e3680","url":"manifest.webmanifest"}]),H();var K=e=>(e.headers.get(`content-type`)??``).startsWith(`text/html`);F(({url:e})=>e.pathname.startsWith(`/assets/`),new G({cacheName:`ppm-assets`,plugins:[{cacheWillUpdate:async({response:e})=>K(e)?null:e,cachedResponseWillBeUsed:async({cachedResponse:e})=>e&&K(e)?null:e}]})),self.skipWaiting(),self.addEventListener(`activate`,()=>{self.clients.claim()});
|
|
1
|
+
try{self[`workbox:core:7.3.0`]&&_()}catch{}var e=(e,...t)=>{let n=e;return t.length>0&&(n+=` :: ${JSON.stringify(t)}`),n},t=class extends Error{constructor(t,n){let r=e(t,n);super(r),this.name=t,this.details=n}},n={googleAnalytics:`googleAnalytics`,precache:`precache-v2`,prefix:`workbox`,runtime:`runtime`,suffix:typeof registration<`u`?registration.scope:``},r=e=>[n.prefix,e,n.suffix].filter(e=>e&&e.length>0).join(`-`),i=e=>{for(let t of Object.keys(n))e(t)},a={updateDetails:e=>{i(t=>{typeof e[t]==`string`&&(n[t]=e[t])})},getGoogleAnalyticsName:e=>e||r(n.googleAnalytics),getPrecacheName:e=>e||r(n.precache),getPrefix:()=>n.prefix,getRuntimeName:e=>e||r(n.runtime),getSuffix:()=>n.suffix};function o(e,t){let n=t();return e.waitUntil(n),n}try{self[`workbox:precaching:7.3.0`]&&_()}catch{}var s=`__WB_REVISION__`;function c(e){if(!e)throw new t(`add-to-cache-list-unexpected-type`,{entry:e});if(typeof e==`string`){let t=new URL(e,location.href);return{cacheKey:t.href,url:t.href}}let{revision:n,url:r}=e;if(!r)throw new t(`add-to-cache-list-unexpected-type`,{entry:e});if(!n){let e=new URL(r,location.href);return{cacheKey:e.href,url:e.href}}let i=new URL(r,location.href),a=new URL(r,location.href);return i.searchParams.set(s,n),{cacheKey:i.href,url:a.href}}var l=class{constructor(){this.updatedURLs=[],this.notUpdatedURLs=[],this.handlerWillStart=async({request:e,state:t})=>{t&&(t.originalRequest=e)},this.cachedResponseWillBeUsed=async({event:e,state:t,cachedResponse:n})=>{if(e.type===`install`&&t&&t.originalRequest&&t.originalRequest instanceof Request){let e=t.originalRequest.url;n?this.notUpdatedURLs.push(e):this.updatedURLs.push(e)}return n}}},u=class{constructor({precacheController:e}){this.cacheKeyWillBeUsed=async({request:e,params:t})=>{let n=t?.cacheKey||this._precacheController.getCacheKeyForURL(e.url);return n?new Request(n,{headers:e.headers}):e},this._precacheController=e}},d;function f(){if(d===void 0){let e=new Response(``);if(`body`in e)try{new Response(e.body),d=!0}catch{d=!1}d=!1}return d}async function p(e,n){let r=null;if(e.url&&(r=new URL(e.url).origin),r!==self.location.origin)throw new t(`cross-origin-copy-response`,{origin:r});let i=e.clone(),a={headers:new Headers(i.headers),status:i.status,statusText:i.statusText},o=n?n(a):a,s=f()?i.body:await i.blob();return new Response(s,o)}var m=e=>new URL(String(e),location.href).href.replace(RegExp(`^${location.origin}`),``);function h(e,t){let n=new URL(e);for(let e of t)n.searchParams.delete(e);return n.href}async function g(e,t,n,r){let i=h(t.url,n);if(t.url===i)return e.match(t,r);let a=Object.assign(Object.assign({},r),{ignoreSearch:!0}),o=await e.keys(t,a);for(let t of o)if(i===h(t.url,n))return e.match(t,r)}var v=class{constructor(){this.promise=new Promise((e,t)=>{this.resolve=e,this.reject=t})}},y=new Set;async function b(){for(let e of y)await e()}function x(e){return new Promise(t=>setTimeout(t,e))}try{self[`workbox:strategies:7.3.0`]&&_()}catch{}function S(e){return typeof e==`string`?new Request(e):e}var C=class{constructor(e,t){this._cacheKeys={},Object.assign(this,t),this.event=t.event,this._strategy=e,this._handlerDeferred=new v,this._extendLifetimePromises=[],this._plugins=[...e.plugins],this._pluginStateMap=new Map;for(let e of this._plugins)this._pluginStateMap.set(e,{});this.event.waitUntil(this._handlerDeferred.promise)}async fetch(e){let{event:n}=this,r=S(e);if(r.mode===`navigate`&&n instanceof FetchEvent&&n.preloadResponse){let e=await n.preloadResponse;if(e)return e}let i=this.hasCallback(`fetchDidFail`)?r.clone():null;try{for(let e of this.iterateCallbacks(`requestWillFetch`))r=await e({request:r.clone(),event:n})}catch(e){if(e instanceof Error)throw new t(`plugin-error-request-will-fetch`,{thrownErrorMessage:e.message})}let a=r.clone();try{let e;e=await fetch(r,r.mode===`navigate`?void 0:this._strategy.fetchOptions);for(let t of this.iterateCallbacks(`fetchDidSucceed`))e=await t({event:n,request:a,response:e});return e}catch(e){throw i&&await this.runCallbacks(`fetchDidFail`,{error:e,event:n,originalRequest:i.clone(),request:a.clone()}),e}}async fetchAndCachePut(e){let t=await this.fetch(e),n=t.clone();return this.waitUntil(this.cachePut(e,n)),t}async cacheMatch(e){let t=S(e),n,{cacheName:r,matchOptions:i}=this._strategy,a=await this.getCacheKey(t,`read`),o=Object.assign(Object.assign({},i),{cacheName:r});n=await caches.match(a,o);for(let e of this.iterateCallbacks(`cachedResponseWillBeUsed`))n=await e({cacheName:r,matchOptions:i,cachedResponse:n,request:a,event:this.event})||void 0;return n}async cachePut(e,n){let r=S(e);await x(0);let i=await this.getCacheKey(r,`write`);if(!n)throw new t(`cache-put-with-no-response`,{url:m(i.url)});let a=await this._ensureResponseSafeToCache(n);if(!a)return!1;let{cacheName:o,matchOptions:s}=this._strategy,c=await self.caches.open(o),l=this.hasCallback(`cacheDidUpdate`),u=l?await g(c,i.clone(),[`__WB_REVISION__`],s):null;try{await c.put(i,l?a.clone():a)}catch(e){if(e instanceof Error)throw e.name===`QuotaExceededError`&&await b(),e}for(let e of this.iterateCallbacks(`cacheDidUpdate`))await e({cacheName:o,oldResponse:u,newResponse:a.clone(),request:i,event:this.event});return!0}async getCacheKey(e,t){let n=`${e.url} | ${t}`;if(!this._cacheKeys[n]){let r=e;for(let e of this.iterateCallbacks(`cacheKeyWillBeUsed`))r=S(await e({mode:t,request:r,event:this.event,params:this.params}));this._cacheKeys[n]=r}return this._cacheKeys[n]}hasCallback(e){for(let t of this._strategy.plugins)if(e in t)return!0;return!1}async runCallbacks(e,t){for(let n of this.iterateCallbacks(e))await n(t)}*iterateCallbacks(e){for(let t of this._strategy.plugins)if(typeof t[e]==`function`){let n=this._pluginStateMap.get(t);yield r=>{let i=Object.assign(Object.assign({},r),{state:n});return t[e](i)}}}waitUntil(e){return this._extendLifetimePromises.push(e),e}async doneWaiting(){for(;this._extendLifetimePromises.length;){let e=this._extendLifetimePromises.splice(0),t=(await Promise.allSettled(e)).find(e=>e.status===`rejected`);if(t)throw t.reason}}destroy(){this._handlerDeferred.resolve(null)}async _ensureResponseSafeToCache(e){let t=e,n=!1;for(let e of this.iterateCallbacks(`cacheWillUpdate`))if(t=await e({request:this.request,response:t,event:this.event})||void 0,n=!0,!t)break;return n||t&&t.status!==200&&(t=void 0),t}},w=class{constructor(e={}){this.cacheName=a.getRuntimeName(e.cacheName),this.plugins=e.plugins||[],this.fetchOptions=e.fetchOptions,this.matchOptions=e.matchOptions}handle(e){let[t]=this.handleAll(e);return t}handleAll(e){e instanceof FetchEvent&&(e={event:e,request:e.request});let t=e.event,n=typeof e.request==`string`?new Request(e.request):e.request,r=`params`in e?e.params:void 0,i=new C(this,{event:t,request:n,params:r}),a=this._getResponse(i,n,t);return[a,this._awaitComplete(a,i,n,t)]}async _getResponse(e,n,r){await e.runCallbacks(`handlerWillStart`,{event:r,request:n});let i;try{if(i=await this._handle(n,e),!i||i.type===`error`)throw new t(`no-response`,{url:n.url})}catch(t){if(t instanceof Error){for(let a of e.iterateCallbacks(`handlerDidError`))if(i=await a({error:t,event:r,request:n}),i)break}if(!i)throw t}for(let t of e.iterateCallbacks(`handlerWillRespond`))i=await t({event:r,request:n,response:i});return i}async _awaitComplete(e,t,n,r){let i,a;try{i=await e}catch{}try{await t.runCallbacks(`handlerDidRespond`,{event:r,request:n,response:i}),await t.doneWaiting()}catch(e){e instanceof Error&&(a=e)}if(await t.runCallbacks(`handlerDidComplete`,{event:r,request:n,response:i,error:a}),t.destroy(),a)throw a}},T=class e extends w{constructor(t={}){t.cacheName=a.getPrecacheName(t.cacheName),super(t),this._fallbackToNetwork=t.fallbackToNetwork!==!1,this.plugins.push(e.copyRedirectedCacheableResponsesPlugin)}async _handle(e,t){return await t.cacheMatch(e)||(t.event&&t.event.type===`install`?await this._handleInstall(e,t):await this._handleFetch(e,t))}async _handleFetch(e,n){let r,i=n.params||{};if(this._fallbackToNetwork){let t=i.integrity,a=e.integrity,o=!a||a===t;r=await n.fetch(new Request(e,{integrity:e.mode===`no-cors`?void 0:a||t})),t&&o&&e.mode!==`no-cors`&&(this._useDefaultCacheabilityPluginIfNeeded(),await n.cachePut(e,r.clone()))}else throw new t(`missing-precache-entry`,{cacheName:this.cacheName,url:e.url});return r}async _handleInstall(e,n){this._useDefaultCacheabilityPluginIfNeeded();let r=await n.fetch(e);if(!await n.cachePut(e,r.clone()))throw new t(`bad-precaching-response`,{url:e.url,status:r.status});return r}_useDefaultCacheabilityPluginIfNeeded(){let t=null,n=0;for(let[r,i]of this.plugins.entries())i!==e.copyRedirectedCacheableResponsesPlugin&&(i===e.defaultPrecacheCacheabilityPlugin&&(t=r),i.cacheWillUpdate&&n++);n===0?this.plugins.push(e.defaultPrecacheCacheabilityPlugin):n>1&&t!==null&&this.plugins.splice(t,1)}};T.defaultPrecacheCacheabilityPlugin={async cacheWillUpdate({response:e}){return!e||e.status>=400?null:e}},T.copyRedirectedCacheableResponsesPlugin={async cacheWillUpdate({response:e}){return e.redirected?await p(e):e}};var E=class{constructor({cacheName:e,plugins:t=[],fallbackToNetwork:n=!0}={}){this._urlsToCacheKeys=new Map,this._urlsToCacheModes=new Map,this._cacheKeysToIntegrities=new Map,this._strategy=new T({cacheName:a.getPrecacheName(e),plugins:[...t,new u({precacheController:this})],fallbackToNetwork:n}),this.install=this.install.bind(this),this.activate=this.activate.bind(this)}get strategy(){return this._strategy}precache(e){this.addToCacheList(e),this._installAndActiveListenersAdded||=(self.addEventListener(`install`,this.install),self.addEventListener(`activate`,this.activate),!0)}addToCacheList(e){let n=[];for(let r of e){typeof r==`string`?n.push(r):r&&r.revision===void 0&&n.push(r.url);let{cacheKey:e,url:i}=c(r),a=typeof r!=`string`&&r.revision?`reload`:`default`;if(this._urlsToCacheKeys.has(i)&&this._urlsToCacheKeys.get(i)!==e)throw new t(`add-to-cache-list-conflicting-entries`,{firstEntry:this._urlsToCacheKeys.get(i),secondEntry:e});if(typeof r!=`string`&&r.integrity){if(this._cacheKeysToIntegrities.has(e)&&this._cacheKeysToIntegrities.get(e)!==r.integrity)throw new t(`add-to-cache-list-conflicting-integrities`,{url:i});this._cacheKeysToIntegrities.set(e,r.integrity)}if(this._urlsToCacheKeys.set(i,e),this._urlsToCacheModes.set(i,a),n.length>0){let e=`Workbox is precaching URLs without revision info: ${n.join(`, `)}\nThis is generally NOT safe. Learn more at https://bit.ly/wb-precache`;console.warn(e)}}}install(e){return o(e,async()=>{let t=new l;this.strategy.plugins.push(t);for(let[t,n]of this._urlsToCacheKeys){let r=this._cacheKeysToIntegrities.get(n),i=this._urlsToCacheModes.get(t),a=new Request(t,{integrity:r,cache:i,credentials:`same-origin`});await Promise.all(this.strategy.handleAll({params:{cacheKey:n},request:a,event:e}))}let{updatedURLs:n,notUpdatedURLs:r}=t;return{updatedURLs:n,notUpdatedURLs:r}})}activate(e){return o(e,async()=>{let e=await self.caches.open(this.strategy.cacheName),t=await e.keys(),n=new Set(this._urlsToCacheKeys.values()),r=[];for(let i of t)n.has(i.url)||(await e.delete(i),r.push(i.url));return{deletedURLs:r}})}getURLsToCacheKeys(){return this._urlsToCacheKeys}getCachedURLs(){return[...this._urlsToCacheKeys.keys()]}getCacheKeyForURL(e){let t=new URL(e,location.href);return this._urlsToCacheKeys.get(t.href)}getIntegrityForCacheKey(e){return this._cacheKeysToIntegrities.get(e)}async matchPrecache(e){let t=e instanceof Request?e.url:e,n=this.getCacheKeyForURL(t);if(n)return(await self.caches.open(this.strategy.cacheName)).match(n)}createHandlerBoundToURL(e){let n=this.getCacheKeyForURL(e);if(!n)throw new t(`non-precached-url`,{url:e});return t=>(t.request=new Request(e),t.params=Object.assign({cacheKey:n},t.params),this.strategy.handle(t))}},D,O=()=>(D||=new E,D);try{self[`workbox:routing:7.3.0`]&&_()}catch{}var k=e=>e&&typeof e==`object`?e:{handle:e},A=class{constructor(e,t,n=`GET`){this.handler=k(t),this.match=e,this.method=n}setCatchHandler(e){this.catchHandler=k(e)}},j=class extends A{constructor(e,t,n){super(({url:t})=>{let n=e.exec(t.href);if(n&&!(t.origin!==location.origin&&n.index!==0))return n.slice(1)},t,n)}},M=class{constructor(){this._routes=new Map,this._defaultHandlerMap=new Map}get routes(){return this._routes}addFetchListener(){self.addEventListener(`fetch`,(e=>{let{request:t}=e,n=this.handleRequest({request:t,event:e});n&&e.respondWith(n)}))}addCacheListener(){self.addEventListener(`message`,(e=>{if(e.data&&e.data.type===`CACHE_URLS`){let{payload:t}=e.data,n=Promise.all(t.urlsToCache.map(t=>{typeof t==`string`&&(t=[t]);let n=new Request(...t);return this.handleRequest({request:n,event:e})}));e.waitUntil(n),e.ports&&e.ports[0]&&n.then(()=>e.ports[0].postMessage(!0))}}))}handleRequest({request:e,event:t}){let n=new URL(e.url,location.href);if(!n.protocol.startsWith(`http`))return;let r=n.origin===location.origin,{params:i,route:a}=this.findMatchingRoute({event:t,request:e,sameOrigin:r,url:n}),o=a&&a.handler,s=e.method;if(!o&&this._defaultHandlerMap.has(s)&&(o=this._defaultHandlerMap.get(s)),!o)return;let c;try{c=o.handle({url:n,request:e,event:t,params:i})}catch(e){c=Promise.reject(e)}let l=a&&a.catchHandler;return c instanceof Promise&&(this._catchHandler||l)&&(c=c.catch(async r=>{if(l)try{return await l.handle({url:n,request:e,event:t,params:i})}catch(e){e instanceof Error&&(r=e)}if(this._catchHandler)return this._catchHandler.handle({url:n,request:e,event:t});throw r})),c}findMatchingRoute({url:e,sameOrigin:t,request:n,event:r}){let i=this._routes.get(n.method)||[];for(let a of i){let i,o=a.match({url:e,sameOrigin:t,request:n,event:r});if(o)return i=o,(Array.isArray(i)&&i.length===0||o.constructor===Object&&Object.keys(o).length===0||typeof o==`boolean`)&&(i=void 0),{route:a,params:i}}return{}}setDefaultHandler(e,t=`GET`){this._defaultHandlerMap.set(t,k(e))}setCatchHandler(e){this._catchHandler=k(e)}registerRoute(e){this._routes.has(e.method)||this._routes.set(e.method,[]),this._routes.get(e.method).push(e)}unregisterRoute(e){if(!this._routes.has(e.method))throw new t(`unregister-route-but-not-found-with-method`,{method:e.method});let n=this._routes.get(e.method).indexOf(e);if(n>-1)this._routes.get(e.method).splice(n,1);else throw new t(`unregister-route-route-not-registered`)}},N,P=()=>(N||(N=new M,N.addFetchListener(),N.addCacheListener()),N);function F(e,n,r){let i;if(typeof e==`string`){let t=new URL(e,location.href);i=new A(({url:e})=>e.href===t.href,n,r)}else if(e instanceof RegExp)i=new j(e,n,r);else if(typeof e==`function`)i=new A(e,n,r);else if(e instanceof A)i=e;else throw new t(`unsupported-route-type`,{moduleName:`workbox-routing`,funcName:`registerRoute`,paramName:`capture`});return P().registerRoute(i),i}function I(e,t=[]){for(let n of[...e.searchParams.keys()])t.some(e=>e.test(n))&&e.searchParams.delete(n);return e}function*L(e,{ignoreURLParametersMatching:t=[/^utm_/,/^fbclid$/],directoryIndex:n=`index.html`,cleanURLs:r=!0,urlManipulation:i}={}){let a=new URL(e,location.href);a.hash=``,yield a.href;let o=I(a,t);if(yield o.href,n&&o.pathname.endsWith(`/`)){let e=new URL(o.href);e.pathname+=n,yield e.href}if(r){let e=new URL(o.href);e.pathname+=`.html`,yield e.href}if(i){let e=i({url:a});for(let t of e)yield t.href}}var R=class extends A{constructor(e,t){super(({request:n})=>{let r=e.getURLsToCacheKeys();for(let i of L(n.url,t)){let t=r.get(i);if(t)return{cacheKey:t,integrity:e.getIntegrityForCacheKey(t)}}},e.strategy)}};function z(e){F(new R(O(),e))}var B=`-precache-`,V=async(e,t=B)=>{let n=(await self.caches.keys()).filter(n=>n.includes(t)&&n.includes(self.registration.scope)&&n!==e);return await Promise.all(n.map(e=>self.caches.delete(e))),n};function H(){self.addEventListener(`activate`,(e=>{let t=a.getPrecacheName();e.waitUntil(V(t).then(e=>{}))}))}function U(e){O().precache(e)}function W(e,t){U(e),z(t)}var G=class extends w{async _handle(e,n){let r=await n.cacheMatch(e),i;if(!r)try{r=await n.fetchAndCachePut(e)}catch(e){e instanceof Error&&(i=e)}if(!r)throw new t(`no-response`,{url:e.url,error:i});return r}};W([{"revision":"a526faed8aec2751ec33b6b98d4b6964","url":"index.html"},{"revision":"26dccd02a2ef7522892015154f5e3680","url":"manifest.webmanifest"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-512.svg"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-192.svg"},{"revision":null,"url":"assets/index-Dh5apC3I.css"},{"revision":null,"url":"assets/index-CyDLBddf.js"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-192.svg"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-512.svg"},{"revision":"26dccd02a2ef7522892015154f5e3680","url":"manifest.webmanifest"}]),H();var K=e=>(e.headers.get(`content-type`)??``).startsWith(`text/html`);F(({url:e})=>e.pathname.startsWith(`/assets/`),new G({cacheName:`ppm-assets`,plugins:[{cacheWillUpdate:async({response:e})=>K(e)?null:e,cachedResponseWillBeUsed:async({cachedResponse:e})=>e&&K(e)?null:e}]})),self.skipWaiting(),self.addEventListener(`activate`,()=>{self.clients.claim()});
|
package/dist/web/sw.js.br
CHANGED
|
Binary file
|
package/dist/web/sw.js.gz
CHANGED
|
Binary file
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
# Phase 0 report — Android Emulator trong PPM
|
|
2
|
+
|
|
3
|
+
Ngày: 2026-09-21. Đối chiếu plan: `docs/architecture/android-emulator-plan-260920.md`.
|
|
4
|
+
Spike: `spikes/android/` (không nằm trong build, không ship).
|
|
5
|
+
|
|
6
|
+
## 1. Kết luận cổng (gate)
|
|
7
|
+
|
|
8
|
+
Plan §9 đặt gate Phase 0 là: *"boot/stream/control/cleanup thật và ngân sách sơ bộ đạt"*.
|
|
9
|
+
|
|
10
|
+
**Phần đã đo: đạt.** Boot, stream, control và cleanup đều chạy thật trên host Linux này, và mọi
|
|
11
|
+
chỉ số đo được đều nằm trong ngân sách §10 — phần lớn là cách ngân sách một quãng rộng.
|
|
12
|
+
|
|
13
|
+
**Phần chưa đo: click-to-visible, LAN/tunnel, reconnect, soak.** Bốn mục này đều cần một viewer
|
|
14
|
+
trong trình duyệt mà Phase 0 không dựng. Chúng **chưa được chứng minh**, không phải đã đạt.
|
|
15
|
+
|
|
16
|
+
Quan trọng hơn cả các con số: hai giả định trong plan sai, và ba thứ plan không biết đã xuất
|
|
17
|
+
hiện — trong đó có một thứ làm bàn phím chết im lặng.
|
|
18
|
+
|
|
19
|
+
## 2. Môi trường đo
|
|
20
|
+
|
|
21
|
+
| Thành phần | Phiên bản |
|
|
22
|
+
|---|---|
|
|
23
|
+
| Host | Linux 7.2.6 CachyOS, i9-12900K (24 luồng), Intel UHD 770, 62 GiB RAM |
|
|
24
|
+
| Bun | 1.3.11 |
|
|
25
|
+
| Android emulator | 36.5.10.0, build 15081367 |
|
|
26
|
+
| adb | 1.0.41 (37.0.0-android-tools) |
|
|
27
|
+
| ffmpeg | n9.0.1 |
|
|
28
|
+
| `@grpc/grpc-js` | 1.14.0 |
|
|
29
|
+
| `@grpc/proto-loader` | 0.7.15 |
|
|
30
|
+
| AVD test | `ppm_spike_test`, API 35 `google_apis_playstore_tablet` x86_64, pixel_6, 1080x2400 @420dpi |
|
|
31
|
+
| GPU emulator | `swiftshader_indirect` (phần mềm) |
|
|
32
|
+
|
|
33
|
+
AVD test do task tạo trong `ANDROID_AVD_HOME=/home/thawngho/.android-spike-avd`. `Pixel_9` và
|
|
34
|
+
`Pixel_Tablet` của người dùng **không bị đụng**; đã verify cả hai chiều: emulator chỉ thấy AVD
|
|
35
|
+
spike khi biến môi trường được set, và chỉ thấy AVD người dùng khi không.
|
|
36
|
+
|
|
37
|
+
## 3. ADR-A — gRPC dưới Bun: **đạt**
|
|
38
|
+
|
|
39
|
+
| Phép đo | Kết quả |
|
|
40
|
+
|---|---|
|
|
41
|
+
| `getStatus` lần đầu (gồm bắt tay HTTP/2) | 1617.9 ms |
|
|
42
|
+
| `getStatus` steady-state, 50 lần | p50 **0.68 ms**, p95 1.32 ms, max 1.95 ms |
|
|
43
|
+
| `getScreenshot` RGB888 full-res | 7.776.000 byte (đúng `1080×2400×3`) trong 21.3 ms |
|
|
44
|
+
| `getDisplayConfigurations` | OK, 1 display, `maxDisplays: 11` |
|
|
45
|
+
| Deadline unary | Fire đúng, `DEADLINE_EXCEEDED` |
|
|
46
|
+
| Cancel stream | `CANCELLED`, **0 frame giao sau `cancel()`** |
|
|
47
|
+
| 25 vòng create/cancel | Emulator vẫn trả lời, client đóng sạch |
|
|
48
|
+
|
|
49
|
+
p50 0.68 ms nghĩa là đường **input** qua grpc-js gần như không tốn gì. Không cần lo phần này.
|
|
50
|
+
|
|
51
|
+
### Compiled binary: hỏng theo cách đã đoán trước, và đã sửa
|
|
52
|
+
|
|
53
|
+
Chạy binary `bun build --compile` từ thư mục ngoài checkout:
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
ENOENT at loadProtosWithOptionsSync (/$bunfs/root/spike-naive:23060:37)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
`@grpc/proto-loader.loadSync()` đọc file `.proto` từ đĩa lúc chạy; trong binary đường dẫn trỏ vào
|
|
60
|
+
`/$bunfs/root/` nên không có file. Đây đúng bài học "compiled PPM không với tới node_modules của
|
|
61
|
+
chính nó" trong CLAUDE.md, và nó xác nhận quyết định *"build-time generate"* của ADR-A bằng một
|
|
62
|
+
thất bại đo được chứ không phải suy luận.
|
|
63
|
+
|
|
64
|
+
Cách sửa đã chứng minh: `bun gen-proto-json.ts` sinh descriptor JSON (48 KiB) → `import` vào module
|
|
65
|
+
→ `protoLoader.fromJSON()`. Sau đó binary chạy từ `/tmp/.../elsewhere` với `getStatus` 17.5 ms, và
|
|
66
|
+
toàn bộ bài cancellation/deadline/cleanup pass **giống hệt bản chạy từ source**.
|
|
67
|
+
|
|
68
|
+
→ **`loadSync` không được phép xuất hiện trong code PPM.** Chỉ `fromJSON` với descriptor nhúng.
|
|
69
|
+
|
|
70
|
+
## 4. ADR-B — media: đo xong, và nghi ngờ lớn nhất về nó là **sai**
|
|
71
|
+
|
|
72
|
+
Plan tính 720p30 RGB = 83 MB/s và lo grpc-js không kham nổi. Đo thật, với chuyển động liên tục
|
|
73
|
+
(stopwatch đang chạy — xác minh 8/8 frame khác nhau trước khi đo):
|
|
74
|
+
|
|
75
|
+
| Cấu hình | fps | Băng thông | gap p50/p95/max | Rớt frame |
|
|
76
|
+
|---|---|---|---|---|
|
|
77
|
+
| RGB888 720×1600 | 39.9 | 131.9 MiB/s | 26.7 / 37.8 / 45.8 ms | **0** |
|
|
78
|
+
| RGB888 1080×2400 | 40.0 | **297.4 MiB/s** | 23.2 / 39.1 / 45.9 ms | **0** |
|
|
79
|
+
| MMAP 720×1600 | 39.8 | **0 byte qua gRPC** | 23.7 / 37.9 / 48.3 ms | 0 |
|
|
80
|
+
|
|
81
|
+
grpc-js dưới Bun tải được **297 MiB/s** ở full-res mà không rớt frame nào (seq liên tục 1..398).
|
|
82
|
+
Ngưỡng 83 MB/s mà plan lo không phải là ngưỡng.
|
|
83
|
+
|
|
84
|
+
**Màn hình tĩnh thì gần như miễn phí**: 2.1 fps, gap đều 500 ms, không rớt frame. Emulator chỉ gửi
|
|
85
|
+
khi khung hình đổi, cộng một nhịp giữ 500 ms. Rất hợp với ngân sách "Idle/ẩn" §10.
|
|
86
|
+
|
|
87
|
+
> Cảnh báo cho người đo sau: đo trên màn hình tĩnh cho ra **2 fps** và trông y như một giới hạn
|
|
88
|
+
> cứng của `streamScreenshot`. Không phải. Swipe rời rạc cũng không đủ — nó chỉ tạo hai trạng thái
|
|
89
|
+
> (đo được: 2/8 frame khác nhau). Phải có nguồn động thật; stopwatch của app Clock là nguồn rẻ nhất.
|
|
90
|
+
|
|
91
|
+
### Pipeline đầy đủ gRPC RGB → ffmpeg → H.264
|
|
92
|
+
|
|
93
|
+
720×1600@30, cap 4 Mbit/s, 12 giây: 359 frame vào ffmpeg, **`ffprobe` đọc ra đúng 360 frame**,
|
|
94
|
+
h264/yuv420p, bitrate **3.90 Mbit/s** — nằm trong mục tiêu 2–6 Mbps của §10.
|
|
95
|
+
|
|
96
|
+
| Encoder | CPU ffmpeg | RSS | Bitrate | Frame decode |
|
|
97
|
+
|---|---|---|---|---|
|
|
98
|
+
| `libx264` veryfast/zerolatency | 64–80% một core (tăng dần) | ~103 MB | 4.10 Mbit/s | 390 |
|
|
99
|
+
| **`h264_vaapi`** | **33.1–33.6%** (phẳng) | ~97 MB | 4.03 Mbit/s | 390 |
|
|
100
|
+
|
|
101
|
+
VAAPI dùng **nửa CPU**, phẳng, cùng bitrate, cùng số frame. Cả `h264_vaapi` và `h264_qsv` chạy được
|
|
102
|
+
trên Intel UHD 770 này. Lưu ý `ffmpeg -encoders` cũng liệt kê `h264_amf` và `h264_nvenc` trên máy
|
|
103
|
+
Intel — đúng lý do `workingEncoders()` phải test-encode thật thay vì đọc danh sách.
|
|
104
|
+
|
|
105
|
+
### Quyết định ADR-B
|
|
106
|
+
|
|
107
|
+
**Giữ option 1 (gRPC RGB → ffmpeg → H.264) làm đường chính.** Không cần chuyển sang scrcpy.
|
|
108
|
+
Lý do là số đo, không phải sở thích: throughput dư 3–4 lần, không rớt frame, input latency p50
|
|
109
|
+
dưới 1 ms, và pipeline đã cho ra stream H.264 decode được đúng số frame.
|
|
110
|
+
|
|
111
|
+
**MMAP để dành, chưa dùng ở v1.** Nó chạy và đưa pixel ra khỏi gRPC hoàn toàn, nhưng fps không
|
|
112
|
+
đổi vì gRPC vốn đã không phải nút thắt. Nó là đòn bẩy để giảm CPU/bộ nhớ khi có nhiều máy cùng
|
|
113
|
+
chạy, và proto tự cảnh báo *"the mmap can result in tearing"*. Đưa vào khi có lý do đo được.
|
|
114
|
+
|
|
115
|
+
## 5. Ba thứ plan không biết
|
|
116
|
+
|
|
117
|
+
### 5.1 `ImageTransport.MMAP` — ADR-B thiếu hẳn một phương án
|
|
118
|
+
|
|
119
|
+
`emulator_controller.proto` field 6 cho **client sở hữu** một handle shm/mmap để emulator ghi frame
|
|
120
|
+
vào; gRPC khi đó chỉ mang metadata (`seq`, `timestampUs`). Đo được: 318/318 frame có pixel thật
|
|
121
|
+
trong buffer chung, **0 byte** pixel đi qua gRPC. Bảng phương án của ADR-B nên có thêm dòng này.
|
|
122
|
+
|
|
123
|
+
### 5.2 Emulator tự scale phía server — nhưng là **hộp bao**, không phải kích thước yêu cầu
|
|
124
|
+
|
|
125
|
+
`ImageFormat.width/height` làm emulator scale **trước khi gửi**: xin 720×1600 trả về đúng
|
|
126
|
+
720×1600 (3.456.000 byte, khớp chính xác). Không cần gửi full-res rồi scale ở host. Plan không nói.
|
|
127
|
+
|
|
128
|
+
**Đính chính ở Phase 2:** trả đúng 720×1600 là vì con số đó khớp tỉ lệ của thiết bị. Proto nói rõ
|
|
129
|
+
hơn thế: *"The returned image will never exceed the given width, but can be less"*, scale *"while
|
|
130
|
+
maintaining the aspect ratio of the device"* — tức đây là **hộp bao**, không phải kích thước ra
|
|
131
|
+
lệnh. Hai hệ quả: (1) phải xin hộp **vuông** `H×H` để trần áp lên cạnh dài, nếu không một guest
|
|
132
|
+
xoay ngang lọt vào hộp dọc sẽ teo lại còn một dải mỏng; (2) kích thước thật phải đọc từ
|
|
133
|
+
`format.width/height` của **chính frame** (là trường output), không được giả định — và đó cũng là
|
|
134
|
+
thứ làm việc xoay màn hình chạy đúng mà không phải đoán.
|
|
135
|
+
|
|
136
|
+
### 5.3 ~~Thứ tự pixel là **bottom-up**~~ — **SAI, đã đo lại ở Phase 2**
|
|
137
|
+
|
|
138
|
+
Mục này từng ghi: *proto nói "from left to right and bottom up", nên chuỗi filter ffmpeg phải có
|
|
139
|
+
`vflip`*. Đó là **chép lại comment trong proto, chưa từng đo**. Phase 2 đo trực tiếp bằng cách so
|
|
140
|
+
từng hàng buffer gRPC với `adb exec-out screencap` (ảnh này chắc chắn top-down):
|
|
141
|
+
|
|
142
|
+
| căn hàng | chênh lệch trung bình / pixel |
|
|
143
|
+
|---|---|
|
|
144
|
+
| `grpc[y]` ↔ `screencap[y]` | **1.21** |
|
|
145
|
+
| `grpc[y]` ↔ `screencap[h-1-y]` | **37.15** |
|
|
146
|
+
|
|
147
|
+
Trên emulator 36.5.10 buffer là **top-down**. Thêm `vflip` sẽ làm **mọi màn hình lộn ngược**, và
|
|
148
|
+
không có lỗi nào báo. Đã bỏ khỏi `encoderArgs`; `tests/e2e/android-e2e.ts` giữ lại phép so này làm
|
|
149
|
+
chốt chặn hồi quy, phòng khi một bản emulator sau này đúng là bottom-up thật.
|
|
150
|
+
|
|
151
|
+
Bài học chung: một câu trong tài liệu vendor không phải là phép đo.
|
|
152
|
+
|
|
153
|
+
### 5.4 Toạ độ `sendTouch` là **panel gốc chưa xoay** (đo ở Phase 2)
|
|
154
|
+
|
|
155
|
+
Proto chỉ nói *"the physical location on the screen"*. Đo bằng `getevent` trên chính guest: màn
|
|
156
|
+
hình cảm ứng báo dải tuyệt đối 0..32767, và toạ độ gửi vào được chuẩn hoá theo kích thước
|
|
157
|
+
**panel** (`hw.lcd.width` × `hw.lcd.height`) — gửi x=2000 trên panel rộng 1080 cho ra 60679, tức
|
|
158
|
+
2000/1080 toàn thang, vượt dải và bị kẹp. Điều này **không đổi khi xoay**, nên frame đã xoay phải
|
|
159
|
+
được xoay ngược lại trước khi gửi. Chiều xoay đo riêng cho từng hướng bằng cách chạm vào một điểm
|
|
160
|
+
panel đã biết với `show_touches`, rồi tìm dấu chạm trong frame; mỗi hướng thắng phương án nhì cách
|
|
161
|
+
biệt một bậc (90°: 0.040 so với 0.449; 180°: 0.003 so với 0.433; 270°: 0.080 so với 0.420).
|
|
162
|
+
|
|
163
|
+
### 5.5 Stream ảnh **không mở lại được sau khi cancel** (đo ở Phase 2)
|
|
164
|
+
|
|
165
|
+
Sau khi `streamScreenshot` bị cancel, mọi stream mở sau đó trả **đúng 1 frame** rồi im — kể cả
|
|
166
|
+
trên channel gRPC mới. Hệ quả thiết kế: đổi mức chất lượng **không được** đóng/mở lại stream. PPM
|
|
167
|
+
mở đúng một stream ở trần rung cao nhất cho cả phiên và đổi rung bằng `scale` trong ffmpeg.
|
|
168
|
+
|
|
169
|
+
## 6. Hai chỗ plan nói sai
|
|
170
|
+
|
|
171
|
+
### 6.1 §7 — plan đúng, bản nháp report này từng ghi sai
|
|
172
|
+
|
|
173
|
+
Bản đầu của report kết luận "không có kiểm tra Origin nào cả". **Sai, do grep thiếu thư mục.**
|
|
174
|
+
Lần quét đầu chỉ có `src/server/ws/`, `src/server/middleware/` và `src/server/index.ts`, bỏ sót
|
|
175
|
+
`src/server/routes/`. Kiểm tra đó nằm ở `src/server/routes/remote-desktop.ts:28-46`
|
|
176
|
+
(`assertSessionAllowed`), và đúng là **hostname-only** như plan mô tả — có chủ ý, có comment: dev
|
|
177
|
+
proxy của Vite ghi đè `Host` mà không thêm `X-Forwarded-Host`, nên so cả `host:port` sẽ chặn mọi
|
|
178
|
+
phiên chạy ở chế độ dev. `named-tunnel.ts` và `accounts.ts` cũng có bản sao của khuôn này.
|
|
179
|
+
|
|
180
|
+
Nên §7 của plan giữ nguyên, và lời cảnh báo của nó vẫn đáng giá: dùng lại khuôn đó thì được, nhưng
|
|
181
|
+
nó **không** phải bảo đảm same-origin đầy đủ — một trang khác **port** trên cùng hostname vẫn qua.
|
|
182
|
+
|
|
183
|
+
### 6.2 §12 — Unicode qua gRPC: câu trả lời là **không**
|
|
184
|
+
|
|
185
|
+
Đo với bàn phím hoạt động, ô nhập đã focus, re-verify focus trước từng lần gõ:
|
|
186
|
+
|
|
187
|
+
| Gửi | Nhận | |
|
|
188
|
+
|---|---|---|
|
|
189
|
+
| `text: "hello"` | `hello` | INTACT |
|
|
190
|
+
| `key` h,e,l,l,o | `hello` | INTACT |
|
|
191
|
+
| `text: "café"` | **`caf`** | ký tự có dấu bị nuốt im lặng |
|
|
192
|
+
| `text: "Tiếng Việt"` | `""` | mất sạch |
|
|
193
|
+
| `text: "😀"` | `""` | mất sạch |
|
|
194
|
+
| `text: "日本語"` | `""` | mất sạch |
|
|
195
|
+
|
|
196
|
+
Proto tự ghi giới hạn này (*"only printable ASCII [32-127)"*), và đo khớp. Kiểu hỏng là tệ nhất:
|
|
197
|
+
**không lỗi, chỉ cắt cụt âm thầm**.
|
|
198
|
+
|
|
199
|
+
`setClipboard` thì giữ nguyên `"Tiếng Việt 😀"` — xác minh cả bằng round-trip API lẫn bằng mắt
|
|
200
|
+
(chip gợi ý clipboard của Android hiện đúng chuỗi kèm emoji trong guest).
|
|
201
|
+
|
|
202
|
+
→ **E2E case 3 của plan không thể đạt bằng `sendKey`/`text`.** Toàn bộ chuyện gõ tiếng Việt và
|
|
203
|
+
IME mobile ở §6 phải xây trên đường clipboard, không phải trên `text`.
|
|
204
|
+
|
|
205
|
+
## 7. Bẫy vận hành phát hiện khi đo
|
|
206
|
+
|
|
207
|
+
**`hw.keyboard=no` là mặc định của `avdmanager`, và nó giết bàn phím trong im lặng.**
|
|
208
|
+
Đây là thứ tốn nhiều thời gian nhất của Phase 0. Triệu chứng: gRPC touch chạy hoàn hảo, gRPC
|
|
209
|
+
`sendKey` trả `OK` và **không có tác dụng gì**; `adb shell input text` thì vào bình thường.
|
|
210
|
+
Không phải allowlist (allowlist sẽ trả `PERMISSION_DENIED`), không phải `-no-window` (đã boot lại
|
|
211
|
+
với `-qt-hide-window`, y hệt). Nguyên nhân là một dòng trong `config.ini`:
|
|
212
|
+
|
|
213
|
+
| AVD | `hw.keyboard` |
|
|
214
|
+
|---|---|
|
|
215
|
+
| tạo bằng `avdmanager` | **`no`** |
|
|
216
|
+
| tạo bằng Android Studio | `yes` |
|
|
217
|
+
|
|
218
|
+
Lật thành `yes` rồi boot lại: cả ba đường (`key`, keydown/keyup, evdev `keyCode`) chạy ngay.
|
|
219
|
+
→ Phase 4 (tạo AVD) **bắt buộc** ghi `hw.keyboard=yes`; plan §9 Phase 4 hiện không nhắc.
|
|
220
|
+
|
|
221
|
+
**`-grpc <port>` tắt JWT.** `emulator -help` ghi `-grpc-use-jwt ... (default, disable with -grpc
|
|
222
|
+
flag)`. Ghim port cố định = tự hạ kênh điều khiển xuống không xác thực, trái §7. Luôn discovery.
|
|
223
|
+
Thực tế trên máy này gRPC bật sẵn không cần cờ nào: `Started GRPC server at 127.0.0.1:8554,
|
|
224
|
+
security: Local, auth: +token`, và emulator tự cảnh báo *"Basic token auth should only be used by
|
|
225
|
+
android-studio"* → PPM không phải Studio, đường đúng là JWT.
|
|
226
|
+
|
|
227
|
+
**`ANDROID_AVD_HOME` thay thế chứ không cộng dồn.** Set nó thì AVD của người dùng biến mất khỏi
|
|
228
|
+
`-list-avds`. Plan §5 nói "tôn trọng AVD home overrides" nhưng không nói chiều này: PPM **không**
|
|
229
|
+
được set biến này toàn cục.
|
|
230
|
+
|
|
231
|
+
**`Touch.pressure` phải về 0 khi nhả.** Proto: identifier không nhận pressure 0 thì không được giải
|
|
232
|
+
phóng. Emulator có lưới an toàn riêng — `EventExpiration` mặc định **120 giây** — nhưng 120 s quá
|
|
233
|
+
dài cho UX, nên timeout phía PPM ở §6 vẫn cần và phải ngắn hơn nhiều.
|
|
234
|
+
|
|
235
|
+
**Dừng máy bằng `setVmState SHUTDOWN`** qua gRPC: pid biến mất sau 1–3 giây. Không cần kill theo
|
|
236
|
+
tên process. Nhân tiện xác nhận cảnh báo của plan là thật: `pgrep qemu-system` trên máy này bắt
|
|
237
|
+
trúng **Docker Desktop**.
|
|
238
|
+
|
|
239
|
+
## 8. Hai bẫy pipeline, cả hai đã tái hiện được
|
|
240
|
+
|
|
241
|
+
**Pacing phải tính theo deadline tích luỹ.** Đẩy mọi frame rồi để `-vf fps=30` lọc là lãng phí
|
|
242
|
+
(đo: 318 frame đẩy vào để ra 240). Nhưng cách pace hiển nhiên — "đã qua một chu kỳ kể từ frame
|
|
243
|
+
nhận cuối chưa" — biến nguồn 40 fps thành **20 fps** ở mục tiêu 30 fps: frame sau đến ở 25 ms bị
|
|
244
|
+
loại, frame kế tiếp lấy ở 50 ms. Đo được 242 frame trong 12 giây thay vì 360. Phải dùng
|
|
245
|
+
`nextDue = max(now, nextDue + interval)`; sửa xong ra đúng 360.
|
|
246
|
+
|
|
247
|
+
**Bun `FileSink.write()` chỉ buffer.** Nó trả về số byte ngay lập tức, nên một vòng lặp ghi tự nó
|
|
248
|
+
không chứng minh được gì đã tới ffmpeg; `flush()` mới đẩy. Ghi nhận trung thực: sau khi đã ghi
|
|
249
|
+
đúng bội số nguyên của frame (`remainder 0`, kiểm bằng bộ đếm byte) ffmpeg **vẫn** báo
|
|
250
|
+
`Invalid buffer size ... packet size N < expected frame_size M` ở cuối stream. Thêm 1500 ms drain
|
|
251
|
+
không hết, phần dư còn to hơn — nên giả thuyết "Bun vứt buffer khi `end()`" là **sai**. Đây là
|
|
252
|
+
artifact lúc đóng: nguồn 40 fps nhanh hơn ffmpeg 30 fps nên pipe còn tồn đọng khi close.
|
|
253
|
+
`ffprobe` xác nhận stream ra đủ 360/360 frame, không frame nào hỏng. Pacing làm nó nhỏ đi nhiều.
|
|
254
|
+
**Chưa truy đến tận cùng**; nếu về sau ghi log ffmpeg cho người dùng thì phải xử lý dòng này,
|
|
255
|
+
đừng để nó trông như lỗi.
|
|
256
|
+
|
|
257
|
+
## 9. Chưa đo — không được coi là đã đạt
|
|
258
|
+
|
|
259
|
+
| §10 | Trạng thái |
|
|
260
|
+
|---|---|
|
|
261
|
+
| fps khi chuyển động | ✅ 39.9–40.0 |
|
|
262
|
+
| bitrate 2–6 Mbps | ✅ 3.90–4.13 Mbit/s |
|
|
263
|
+
| first frame ≤3 s | ✅ 29–59 ms |
|
|
264
|
+
| CPU ≤1 core / RSS ≤250 MiB | ✅ VAAPI 0.33 core, 97 MB |
|
|
265
|
+
| idle dừng feed | ✅ 2 fps màn hình tĩnh |
|
|
266
|
+
| **LAN input-to-visible p95 ≤150 ms** | ❌ chưa đo |
|
|
267
|
+
| **Tunnel p95 ≤300 ms** | ❌ chưa đo |
|
|
268
|
+
| **Reconnect ≤5 s** | ❌ chưa đo |
|
|
269
|
+
| **Soak 60 phút** | ❌ chưa đo |
|
|
270
|
+
| **Ảnh hưởng chat/terminal** | ❌ chưa đo |
|
|
271
|
+
| Windows / macOS | ❌ chưa đo, máy này chỉ chứng minh Linux |
|
|
272
|
+
| Trình duyệt thật (Safari/Chrome mobile) | ❌ chưa đo |
|
|
273
|
+
|
|
274
|
+
Bốn mục đầu đều cần viewer trong trình duyệt. Chúng nên là điều kiện vào Phase 2, không phải thứ
|
|
275
|
+
bỏ qua.
|
|
276
|
+
|
|
277
|
+
## 10. Việc phải sửa trong plan trước khi sang Phase 1
|
|
278
|
+
|
|
279
|
+
1. **§7**: không cần sửa — plan đúng. (Bản nháp report này từng ghi ngược lại vì grep thiếu
|
|
280
|
+
`src/server/routes/`; đã đính chính ở §6.1.)
|
|
281
|
+
2. **§12 + §6 + E2E case 3**: ghi Unicode qua `sendKey.text` là **không khả dụng**; đường tiếng Việt
|
|
282
|
+
và IME mobile phải là clipboard.
|
|
283
|
+
3. **ADR-B**: thêm `ImageTransport.MMAP` vào bảng phương án; ghi quyết định giữ option 1 kèm số đo;
|
|
284
|
+
thêm ghi chú scale server-side và pixel bottom-up.
|
|
285
|
+
4. **§9 Phase 4**: bắt buộc `hw.keyboard=yes` khi tạo AVD.
|
|
286
|
+
5. **§5**: ghi `-grpc <port>` tắt JWT nên luôn discovery; ghi `ANDROID_AVD_HOME` thay thế chứ không
|
|
287
|
+
cộng dồn.
|
|
288
|
+
6. **ADR-A**: ghi rõ cấm `protoLoader.loadSync`, chỉ `fromJSON` + descriptor sinh lúc build.
|
|
289
|
+
|
|
290
|
+
## 11. Tái lập
|
|
291
|
+
|
|
292
|
+
```bash
|
|
293
|
+
cd spikes/android
|
|
294
|
+
bun install
|
|
295
|
+
ANDROID_AVD_HOME=... DISPLAY=:0 WINDOW_MODE=qt-hide-window ./boot-test-avd.sh &
|
|
296
|
+
bun discovery.ts # endpoint + auth từ file discovery
|
|
297
|
+
bun smoke-getstatus.ts # ADR-A: gRPC dưới Bun
|
|
298
|
+
bun verify-rpcs.ts # latency, screenshot PNG/RGB888, scale server-side
|
|
299
|
+
bun focus-search.ts # toạ độ chạm, đọc bounds thật từ uiautomator
|
|
300
|
+
bun text-injection.ts # giới hạn Unicode
|
|
301
|
+
W=720 H=1600 bun bench-stream.ts # cần chuyển động thật trên màn hình
|
|
302
|
+
W=720 H=1600 bun bench-mmap.ts
|
|
303
|
+
ENC=h264_vaapi W=720 H=1600 FPS=30 OUT=/tmp/o.h264 bun bench-encode.ts
|
|
304
|
+
bun verify-cancel.ts # deadline / cancel / cleanup
|
|
305
|
+
bun gen-proto-json.ts && bun build --compile --outfile /tmp/s smoke-getstatus.ts
|
|
306
|
+
bun stop-emulator.ts # graceful, không kill theo tên
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
Dọn dẹp: `bun stop-emulator.ts`, rồi xoá `/home/thawngho/.android-spike-avd` và
|
|
310
|
+
`/tmp/ppm-android-spike`. Spike có khởi động adb daemon (trước đó chưa chạy).
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# Phase 2 report — Viewer tab với điều khiển đầy đủ
|
|
2
|
+
|
|
3
|
+
Ngày: 2026-09-21. Cổng Phase 1 đã qua (xem `android-emulator-phase0-report-260921.md` cho Phase 0).
|
|
4
|
+
Bản này ghi **những gì đo được**, đặc biệt là ba chỗ Phase 0 và plan nói sai, và các quyết định
|
|
5
|
+
thiết kế bị chính phép đo ép phải đổi.
|
|
6
|
+
|
|
7
|
+
## 1. Kết quả
|
|
8
|
+
|
|
9
|
+
| | |
|
|
10
|
+
|---|---|
|
|
11
|
+
| E2E mức dịch vụ (`tests/e2e/android-e2e.ts`) | **20/20**, lặp lại 3 lần đều pass |
|
|
12
|
+
| E2E qua server thật (`tests/e2e/android-ws-e2e.ts`) | **18/18** |
|
|
13
|
+
| Unit | 56 test Android (7 file), toàn bộ `tests/unit/web` + `tests/unit/android` 1542 pass |
|
|
14
|
+
| `tsc --noEmit` | 0 lỗi |
|
|
15
|
+
| `bun run build:web` | chunk riêng `android-tab-*.js`, 18.65 kB (gzip 6.23) |
|
|
16
|
+
|
|
17
|
+
Thông lượng đo trên emulator 36.5.10, guest 1080×2400, host Intel UHD 770 + `h264_vaapi`:
|
|
18
|
+
|
|
19
|
+
| | |
|
|
20
|
+
|---|---|
|
|
21
|
+
| Stream nguồn gRPC | **~55 fps, 864×1920, ~260 MiB/s**, giữ nguyên suốt 45 s không tụt |
|
|
22
|
+
| Sau pacing 24 fps (rung `low`) | fed 192 frame / 8 s — đúng bằng mục tiêu |
|
|
23
|
+
| Access unit ra | 199 / 8 s |
|
|
24
|
+
| Keyframe | 14 / 8 s ở GOP nửa giây |
|
|
25
|
+
| Codec | `avc1.64081f`, suy từ SPS thật |
|
|
26
|
+
|
|
27
|
+
## 2. Ba chỗ tài liệu nói sai, và cái giá nếu tin theo
|
|
28
|
+
|
|
29
|
+
### 2.1 `vflip` — plan và Phase 0 đều sai
|
|
30
|
+
|
|
31
|
+
Cả hai ghi *"thứ tự pixel là bottom-up nên chuỗi filter phải có `vflip`"*. Cả hai **chép từ comment
|
|
32
|
+
trong proto**, không đo. So từng hàng buffer gRPC với `adb exec-out screencap` (top-down chắc chắn):
|
|
33
|
+
|
|
34
|
+
| căn hàng | chênh lệch trung bình / pixel |
|
|
35
|
+
|---|---|
|
|
36
|
+
| `grpc[y]` ↔ `screencap[y]` | **1.21** |
|
|
37
|
+
| `grpc[y]` ↔ `screencap[h-1-y]` | **37.15** |
|
|
38
|
+
|
|
39
|
+
Buffer **top-down**. `vflip` sẽ làm mọi màn hình lộn ngược, không lỗi, không log. Đã bỏ. Phép so
|
|
40
|
+
này giữ lại trong `android-e2e.ts` làm chốt hồi quy — hai ảnh phải chụp lúc màn hình **đứng yên**,
|
|
41
|
+
nếu đang cuộn thì biên độ tụt xuống mức nhiễu (đo được: 217 so với 232, so với 1.5 so với 247 khi
|
|
42
|
+
đứng yên).
|
|
43
|
+
|
|
44
|
+
### 2.2 `ImageFormat.width/height` là hộp bao, không phải kích thước ra lệnh
|
|
45
|
+
|
|
46
|
+
Phase 0 kết luận "xin 720×1600 trả đúng 720×1600". Đúng, nhưng vì con số đó **khớp tỉ lệ thiết bị**.
|
|
47
|
+
Proto nói rõ: *"will never exceed the given width, but can be less"*, giữ nguyên tỉ lệ. Hai hệ quả:
|
|
48
|
+
|
|
49
|
+
- Hộp phải **vuông** (`H×H`), để trần áp lên **cạnh dài**. Hộp dọc 720×1600 với một guest xoay
|
|
50
|
+
ngang cho ra 720×324 — mất gần hết độ phân giải đúng lúc người dùng xoay máy.
|
|
51
|
+
- Kích thước thật đọc từ `format.width/height` của **chính frame** (trường output), không giả định.
|
|
52
|
+
Đây cũng là thứ khiến xoay màn hình chạy đúng mà không phải đoán gì: `format.rotation` đi kèm.
|
|
53
|
+
|
|
54
|
+
### 2.3 Stream ảnh không mở lại được sau khi cancel
|
|
55
|
+
|
|
56
|
+
Thiết kế đầu của pipeline đổi rung bằng cách cancel stream rồi mở lại với hộp mới. Đo:
|
|
57
|
+
|
|
58
|
+
| | frame nhận được |
|
|
59
|
+
|---|---|
|
|
60
|
+
| stream đầu tiên | bình thường (7 frame / 4 s màn hình ít đổi) |
|
|
61
|
+
| mở lại ngay trên cùng channel | **1** |
|
|
62
|
+
| mở lại sau 500 ms | **1** |
|
|
63
|
+
| mở lại trên **channel gRPC mới** | **1** |
|
|
64
|
+
|
|
65
|
+
Một stream đã cancel không thay thế được trên emulator 36.5.10. Thiết kế đổi thành: mở **đúng một**
|
|
66
|
+
stream ở trần rung cao nhất (`STREAM_BOX = 1920`) cho cả phiên, đổi rung là `scale=W:H` trong ffmpeg
|
|
67
|
+
cộng một encoder mới. Kết quả tốt hơn thiết kế cũ ở hai điểm khác nữa — không còn cửa sổ mất hình
|
|
68
|
+
lúc chuyển, và không còn một lớp trạng thái emulator để hỏng.
|
|
69
|
+
|
|
70
|
+
Chi phí: rung thấp vẫn nhận 864×1920 qua gRPC rồi mới scale xuống. Đo được là thừa sức — nguồn giữ
|
|
71
|
+
260 MiB/s trong khi `h264_vaapi` chỉ tốn 0.33 core.
|
|
72
|
+
|
|
73
|
+
## 3. Toạ độ chạm — đo bằng `getevent`, không suy luận
|
|
74
|
+
|
|
75
|
+
`Touch.x/y` trong proto chỉ ghi *"the physical location on the screen"*. Đọc thẳng từ input device
|
|
76
|
+
của guest:
|
|
77
|
+
|
|
78
|
+
- Màn hình cảm ứng báo dải tuyệt đối **0..32767**, nên con số ở `getevent` là toạ độ đã chuẩn hoá.
|
|
79
|
+
- Gửi (200, 2000) trên panel 1080×2400 → `X=6067 Y=27305`, khớp `200/1080` và `2000/2400` toàn thang.
|
|
80
|
+
- Gửi x=2000 trên panel rộng 1080 → **60679**, tức `2000/1080` toàn thang: vượt dải, bị kẹp. Vậy
|
|
81
|
+
đơn vị là **pixel panel gốc**, và **không đổi khi xoay**.
|
|
82
|
+
|
|
83
|
+
Chiều xoay đo riêng từng hướng: bật `show_touches`, chạm vào một điểm panel đã biết, tìm dấu chạm
|
|
84
|
+
trong frame bằng **trung vị** các pixel đổi màu. Mỗi hướng thắng phương án nhì cách biệt một bậc:
|
|
85
|
+
|
|
86
|
+
| hướng | phép biến đổi frame → panel | sai số | phương án nhì |
|
|
87
|
+
|---|---|---|---|
|
|
88
|
+
| 0° | `pu=fu, pv=fv` | ~0.006 | — |
|
|
89
|
+
| 90° | `pu=1−fv, pv=fu` | 0.040 | 0.449 |
|
|
90
|
+
| 180° | `pu=1−fu, pv=1−fv` | 0.003 | 0.433 |
|
|
91
|
+
| 270° | `pu=fv, pv=1−fu` | 0.080 | 0.420 |
|
|
92
|
+
|
|
93
|
+
Kiểm lại qua đường thật: frame(320,432) của 1280×576@90 → `frameToDevice` cho panel(270,600) →
|
|
94
|
+
`getevent` báo 8191/8191, mong đợi 8192/8192.
|
|
95
|
+
|
|
96
|
+
Hai bẫy trong chính phép đo này, đáng ghi vì đều làm ra kết quả *trông như đúng*:
|
|
97
|
+
|
|
98
|
+
- Điểm dò đầu tiên là (900, 2000) trên panel 1080×2400. `900/1080 == 2000/2400 == 5/6`, nên hai
|
|
99
|
+
trục cho **cùng một con số** và một vụ đảo trục sẽ vô hình. Điểm dò phải lệch tỉ lệ ở hai trục.
|
|
100
|
+
- Trung bình cộng bị kéo đi bởi bất cứ thứ gì nhấp nháy ở góc màn hình (đồng hồ). Lần đo đầu cho
|
|
101
|
+
0.5 ở mọi hướng — tức là đo cái đồng hồ. **Trung vị** mới ra kết quả.
|
|
102
|
+
|
|
103
|
+
## 4. `streamScreenshot` là stream **theo thay đổi**
|
|
104
|
+
|
|
105
|
+
> **Đính chính 2026-09-21.** Câu "không phải lỗi" bên dưới là sai, và chính cách đóng khung đó đã
|
|
106
|
+
> để lọt một lỗi thật: sự kiện này được coi là chuyện của *phép đo* chứ không phải của *sản phẩm*.
|
|
107
|
+
> Pipeline chỉ nạp ffmpeg khi có khung mới, nên trên màn hình đứng yên luồng H.264 im hẳn và người
|
|
108
|
+
> mở viewer lúc đó kẹt ở "Waiting for the first frame…" cho tới khi guest có gì đó động đậy. Đã
|
|
109
|
+
> sửa: nạp lại khung cuối theo nhịp fps của rung (`feedNow` + pacer trong `android-video.ts`), GOP
|
|
110
|
+
> nửa giây nên người vào xem máy đứng im có keyframe trong vòng 500 ms. Đo trên máy thật, không
|
|
111
|
+
> một thao tác nào: **143 khung, 10 keyframe trong 6 giây**. E2E cũ không thấy vì mọi kiểm tra đều
|
|
112
|
+
> chạy kèm vòng swipe liên tục — nay `android-ws-e2e.ts` tắt vòng đó rồi mới nối viewer C.
|
|
113
|
+
|
|
114
|
+
Không phải lỗi, nhưng làm hỏng mọi phép đo nếu không biết: màn hình đứng yên gần như không sinh
|
|
115
|
+
frame, màn hình **đã tắt** thì không sinh frame nào. Một lần chạy e2e báo 0 access unit trong 8 s
|
|
116
|
+
với pipeline hoàn toàn khoẻ — nguyên nhân là một cú chạm dò toạ độ đã **mở Play Store**, mà không
|
|
117
|
+
có mạng thì đó là một bức ảnh tĩnh. `android-e2e.ts` vì thế phải: đánh thức guest, ghim `stayon`,
|
|
118
|
+
đưa về launcher trước mỗi phép đo, và cuộn màn hình trong lúc đo. Khi số frame nguồn không nhúc
|
|
119
|
+
nhích, nó in luôn cửa sổ đang focus — dòng đó là thứ đã giải được vụ này.
|
|
120
|
+
|
|
121
|
+
## 5. Quyết định thiết kế
|
|
122
|
+
|
|
123
|
+
| Quyết định | Lý do |
|
|
124
|
+
|---|---|
|
|
125
|
+
| Một pipeline **cho mỗi thiết bị**, nhiều viewer dùng chung | Người xem thứ hai không được tốn thêm một stream gRPC và một ffmpeg cho cùng số pixel |
|
|
126
|
+
| Lease điều khiển theo **socket**, heartbeat 5 s / hết hạn 15 s | Hai người cùng chạm vào một guest không phải là tính năng. Máy gập nắp mất lease nhưng **vẫn xem được** |
|
|
127
|
+
| `sessionGeneration` và `geometryGeneration` tách nhau | Một cái trả lời "client này còn được điều khiển không", cái kia "toạ độ này còn mô tả màn hình đang có không" |
|
|
128
|
+
| Chuỗi codec `avc1.PPCCLL` suy từ **SPS thật**, gửi bằng message riêng | `encoderArgs` không đặt profile/level nên giá trị là mặc định của encoder, chỉ biết khi có bitstream. Đoán sai thì `configure()` ném lỗi |
|
|
129
|
+
| Backpressure: **một** ngưỡng, không có thang | `ws.getBufferedAmount()` là hàng đợi userspace của Bun, đã đo là gần như bất động kể cả ở 0.4 Mbit/s qua relay thật (CLAUDE.md). Xây thang trên tín hiệu đó là xây trên cát |
|
|
130
|
+
| Bàn phím đi qua **hidden textarea**, không qua `keydown` | IME gõ tiếng Việt gộp nhiều phím thành một ký tự; đọc `keydown` sẽ ra "Tieesng". Bàn phím ảo điện thoại không báo key code nào cả |
|
|
131
|
+
| Chữ không phải ASCII đi qua **clipboard** | Phase 0 đo: `sendKey.text` im lặng nuốt mọi ký tự ngoài [32,127) |
|
|
132
|
+
| Tab, không phải cửa sổ nổi như Remote Desktop | Chỉ có một desktop host để điều khiển, nhưng có thể có nhiều AVD, và người ta xem máy ảo **cạnh** code đang sửa |
|
|
133
|
+
| Tính năng **tắt mặc định** | Nó dựng một máy ảo. Một máy có sẵn Android SDK không có nghĩa là người dùng đã đồng ý cho PPM chạy emulator trên đó |
|
|
134
|
+
|
|
135
|
+
## 6. Chưa đo — không được coi là đã đạt
|
|
136
|
+
|
|
137
|
+
Giống mục 9 của Phase 0, phần này để tránh tự tin quá mức:
|
|
138
|
+
|
|
139
|
+
- **Độ trễ chạm-đến-thấy** p50/p95, trên LAN và qua tunnel. Chưa đo lần nào.
|
|
140
|
+
- **Reconnect ≤ 5 s** theo §10 của plan: đã có backoff và có `ready` sau khi nối lại, nhưng chưa
|
|
141
|
+
bấm giờ trên mạng thật bị rớt.
|
|
142
|
+
- **Soak 60 phút**: dài nhất đã chạy là 45 s cho stream nguồn.
|
|
143
|
+
- **Ảnh hưởng tới chat/terminal** khi chạy song song.
|
|
144
|
+
- **Windows / macOS**: chưa chạy dòng nào. `h264_qsv`/`h264_vaapi` là đường Linux; `h264_amf` chưa
|
|
145
|
+
từng được chọn ở đây.
|
|
146
|
+
- **Trình duyệt di động thật**: `VideoDecoder` trên Chromium di động đã từng chết sau ~1 GOP ở
|
|
147
|
+
Remote Desktop; hook decoder tự hồi phục, nhưng chưa thử với luồng này.
|
|
148
|
+
- **Nhiều AVD cùng lúc**: `max_concurrent` mặc định là 1 và chưa nâng lên để thử.
|
|
149
|
+
|
|
150
|
+
## 7. Tái lập
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
# 1. AVD thử nghiệm riêng, không đụng Pixel_9 / Pixel_Tablet của người dùng
|
|
154
|
+
bash spikes/android/boot-test-avd.sh &
|
|
155
|
+
|
|
156
|
+
# 2. E2E mức dịch vụ
|
|
157
|
+
ANDROID_EMULATOR_ENABLED=1 PPM_HOME=$(mktemp -d) bun tests/e2e/android-e2e.ts
|
|
158
|
+
|
|
159
|
+
# 3. E2E qua server thật
|
|
160
|
+
ANDROID_EMULATOR_ENABLED=1 bun src/server/index.ts __serve__ 8099 127.0.0.1 dev &
|
|
161
|
+
PPM_BASE=http://127.0.0.1:8099 PPM_TOKEN=<token của ppm.dev.db> \
|
|
162
|
+
PPM_HOME=$(mktemp -d) bun tests/e2e/android-ws-e2e.ts
|
|
163
|
+
```
|