@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
|
@@ -5,6 +5,8 @@ import { existsSync, mkdirSync } from "node:fs";
|
|
|
5
5
|
import { fileService, SecurityError, NotFoundError, ValidationError } from "../../services/file.service.ts";
|
|
6
6
|
import { readSystemFileSync } from "../../services/fs-browse.service.ts";
|
|
7
7
|
import { ok, err } from "../../types/api.ts";
|
|
8
|
+
import type { FileIndexTooLarge } from "../../types/project.ts";
|
|
9
|
+
import { REMOTE_FILE_SEARCH_LIMIT } from "../../shared/file-index-limits.ts";
|
|
8
10
|
import { errorStatus } from "../helpers/error-status.ts";
|
|
9
11
|
import { rangeFileResponse } from "../helpers/range-file-response.ts";
|
|
10
12
|
import { handleMediaProbe, handleMediaTranscode, handleMediaTranscodeStop } from "../helpers/media-route-handlers.ts";
|
|
@@ -63,15 +65,60 @@ fileRoutes.post("/list-batch", async (c) => {
|
|
|
63
65
|
});
|
|
64
66
|
|
|
65
67
|
/**
|
|
66
|
-
*
|
|
68
|
+
* A project's first walk can outlast Bun's 10 s idle timeout — nxsys-workspace took 7.6 s on an
|
|
69
|
+
* idle server and more on a busy one — and Bun then cuts the request off with the walk nearly
|
|
70
|
+
* done. The walk no longer stops the server, so waiting is safe; 30 s is as long as the browser
|
|
71
|
+
* waits for headers. `c.env` is the Bun server that `app.fetch(req, server)` passed.
|
|
72
|
+
*/
|
|
73
|
+
function outlastFirstWalk(c: { env: unknown; req: { raw: Request } }): void {
|
|
74
|
+
(c.env as { timeout?: (request: Request, seconds: number) => void } | undefined)?.timeout?.(c.req.raw, 30);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Most entries a `/files/index/search` answers with. */
|
|
78
|
+
const MAX_INDEX_SEARCH_LIMIT = 500;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* GET /files/index?max=<n>
|
|
67
82
|
* Returns flat array of all project files {path, name} for palette/search.
|
|
68
|
-
* Result is cached;
|
|
83
|
+
* Result is cached; a file change marks it stale, and the stale list is served while it rebuilds.
|
|
84
|
+
* With `max`, a project holding more entries than that answers `FileIndexTooLarge` instead of the
|
|
85
|
+
* list, to be searched with `/files/index/search`.
|
|
69
86
|
*/
|
|
70
|
-
fileRoutes.get("/index", (c) => {
|
|
87
|
+
fileRoutes.get("/index", async (c) => {
|
|
71
88
|
try {
|
|
72
89
|
const projectPath = c.get("projectPath");
|
|
73
|
-
|
|
74
|
-
|
|
90
|
+
outlastFirstWalk(c);
|
|
91
|
+
// The body is serialised and gzipped once per walk, on the index worker: nxsys-workspace's
|
|
92
|
+
// is 22 MB, which cost ~120 ms of the event loop to serialise and compress per download.
|
|
93
|
+
const build = await fileService.buildIndex(projectPath);
|
|
94
|
+
const max = Number(c.req.query("max"));
|
|
95
|
+
if (max > 0 && build.count > max) return c.json(ok<FileIndexTooLarge>({ tooLarge: true, count: build.count }));
|
|
96
|
+
const gzip = (c.req.header("Accept-Encoding") ?? "").includes("gzip");
|
|
97
|
+
return new Response(gzip ? build.gzip : build.json, {
|
|
98
|
+
headers: {
|
|
99
|
+
"Content-Type": "application/json",
|
|
100
|
+
...(gzip && { "Content-Encoding": "gzip", Vary: "Accept-Encoding" }),
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
} catch (e) {
|
|
104
|
+
return c.json(err((e as Error).message), errorStatus(e));
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* GET /files/index/search?q=<query>&kind=file|all&limit=<n>
|
|
110
|
+
* The best entries of the file index for `q`, best first — ranked as the palette ranks a list it
|
|
111
|
+
* holds — searched on the server, for a project whose list is too long to send. `kind=all`
|
|
112
|
+
* includes directories. A blank `q` answers the first entries in list order.
|
|
113
|
+
*/
|
|
114
|
+
fileRoutes.get("/index/search", async (c) => {
|
|
115
|
+
try {
|
|
116
|
+
const projectPath = c.get("projectPath");
|
|
117
|
+
outlastFirstWalk(c);
|
|
118
|
+
const query = c.req.query("q") ?? "";
|
|
119
|
+
const kind = c.req.query("kind") === "all" ? "all" : "file";
|
|
120
|
+
const limit = Math.min(Math.max(Math.trunc(Number(c.req.query("limit"))) || REMOTE_FILE_SEARCH_LIMIT, 1), MAX_INDEX_SEARCH_LIMIT);
|
|
121
|
+
return c.json(ok(await fileService.searchIndex(projectPath, query, kind, limit)));
|
|
75
122
|
} catch (e) {
|
|
76
123
|
return c.json(err((e as Error).message), errorStatus(e));
|
|
77
124
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `GET /api/remote-desktop/capabilities` (read-only, always answerable — includes the host's
|
|
3
3
|
* requirements checklist), `POST /api/remote-desktop/requirements/:id/:action` (host-side
|
|
4
|
-
* fixes: OS permission prompt, open its Settings pane)
|
|
5
|
-
* nonce)
|
|
4
|
+
* fixes: OS permission prompt, open its Settings pane), `POST /api/remote-desktop/session` (mints the single-use WS
|
|
5
|
+
* nonce) and `POST /api/remote-desktop/whep/:ticket` (the WebRTC handshake, forwarded to the
|
|
6
|
+
* session's own loopback relay). Guard style mirrors
|
|
6
7
|
* `named-tunnel.ts`: `authMiddleware` already passes every request through when PPM auth is
|
|
7
8
|
* disabled, so a feature that hands out host control must enforce `auth.enabled` itself, plus
|
|
8
9
|
* a same-origin check so a foreign page can't drive it via ambient browser credentials.
|
|
@@ -15,6 +16,10 @@ import { isRemoteDesktopEnabled } from "../../services/remote-desktop/remote-des
|
|
|
15
16
|
import { mintRemoteDesktopNonce } from "../../services/remote-desktop/remote-desktop-nonce.ts";
|
|
16
17
|
import { listDisplays } from "../../services/remote-desktop/remote-desktop-displays.ts";
|
|
17
18
|
import { remoteDesktopReadiness, runHostAction } from "../../services/remote-desktop/remote-desktop-requirements.ts";
|
|
19
|
+
import { resolveWhepTarget } from "../../services/remote-desktop/remote-desktop-whep-registry.ts";
|
|
20
|
+
import {
|
|
21
|
+
relayStatus, startMediamtxInstall, uninstallMediamtx,
|
|
22
|
+
} from "../../services/remote-desktop/mediamtx-install.service.ts";
|
|
18
23
|
|
|
19
24
|
export const remoteDesktopRoutes = new Hono();
|
|
20
25
|
|
|
@@ -84,3 +89,91 @@ remoteDesktopRoutes.post("/session", (c) => {
|
|
|
84
89
|
if (rejected) return rejected;
|
|
85
90
|
return c.json(ok({ nonce: mintRemoteDesktopNonce(), wsPath: "/ws/remote-desktop" }));
|
|
86
91
|
});
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* WHEP: the browser posts an SDP offer, the relay answers with an SDP answer, and media then
|
|
95
|
+
* flows peer-to-peer over the relay's ICE port.
|
|
96
|
+
*
|
|
97
|
+
* The relay's signalling port is bound to loopback (`mediamtx-config.ts`), so this proxy is
|
|
98
|
+
* the only way to reach it — and it is deliberately **ticket-addressed, never URL-addressed**:
|
|
99
|
+
* the client presents a ticket PPM minted for its own session and handed it over the
|
|
100
|
+
* already-authenticated WebSocket. Forwarding a client-supplied URL here would turn a feature
|
|
101
|
+
* that already carries host control into an SSRF hole.
|
|
102
|
+
*
|
|
103
|
+
* `Location` from the relay is not passed back: it addresses 127.0.0.1 and means nothing to
|
|
104
|
+
* the browser. MediaMTX drops the session on its own when the peer connection goes away.
|
|
105
|
+
*/
|
|
106
|
+
const MAX_SDP_BYTES = 64 * 1024;
|
|
107
|
+
|
|
108
|
+
remoteDesktopRoutes.post("/whep/:ticket", async (c) => {
|
|
109
|
+
const rejected = assertSessionAllowed(c);
|
|
110
|
+
if (rejected) return rejected;
|
|
111
|
+
|
|
112
|
+
const target = resolveWhepTarget(c.req.param("ticket"));
|
|
113
|
+
// One answer for unknown and for expired: a distinguishable pair would say whether a ticket
|
|
114
|
+
// ever existed.
|
|
115
|
+
if (!target) return c.json(err("unknown or expired WebRTC ticket"), 404);
|
|
116
|
+
|
|
117
|
+
const contentType = c.req.header("content-type") ?? "";
|
|
118
|
+
if (!contentType.startsWith("application/sdp")) {
|
|
119
|
+
return c.json(err("expected application/sdp"), 415);
|
|
120
|
+
}
|
|
121
|
+
const offer = await c.req.text();
|
|
122
|
+
if (offer.length > MAX_SDP_BYTES) return c.json(err("SDP offer too large"), 413);
|
|
123
|
+
|
|
124
|
+
let answer: Response;
|
|
125
|
+
try {
|
|
126
|
+
answer = await fetch(target, {
|
|
127
|
+
method: "POST",
|
|
128
|
+
headers: { "content-type": "application/sdp" },
|
|
129
|
+
body: offer,
|
|
130
|
+
});
|
|
131
|
+
} catch {
|
|
132
|
+
// The relay died between the ticket being handed out and this call.
|
|
133
|
+
return c.json(err("the WebRTC relay is not reachable"), 502);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const body = await answer.text();
|
|
137
|
+
if (!answer.ok) return c.json(err(`relay rejected the offer (${answer.status})`), 502);
|
|
138
|
+
return new Response(body, {
|
|
139
|
+
status: 201,
|
|
140
|
+
headers: { "content-type": "application/sdp", "cache-control": "no-store" },
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* The WebRTC relay's own install lifecycle, for the Settings pane.
|
|
146
|
+
*
|
|
147
|
+
* Behind the same guards as `/session`: downloading and unpacking an executable onto the host
|
|
148
|
+
* is host control, and the same-origin check keeps a foreign page from triggering it with
|
|
149
|
+
* ambient credentials.
|
|
150
|
+
*/
|
|
151
|
+
remoteDesktopRoutes.get("/relay", (c) => {
|
|
152
|
+
const rejected = assertSessionAllowed(c);
|
|
153
|
+
if (rejected) return rejected;
|
|
154
|
+
return c.json(ok(relayStatus()));
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
remoteDesktopRoutes.post("/relay/install", (c) => {
|
|
158
|
+
const rejected = assertSessionAllowed(c);
|
|
159
|
+
if (rejected) return rejected;
|
|
160
|
+
try {
|
|
161
|
+
return c.json(ok(startMediamtxInstall()));
|
|
162
|
+
} catch (e) {
|
|
163
|
+
return c.json(err((e as Error).message), 409);
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
remoteDesktopRoutes.post("/relay/uninstall", (c) => {
|
|
168
|
+
const rejected = assertSessionAllowed(c);
|
|
169
|
+
if (rejected) return rejected;
|
|
170
|
+
try {
|
|
171
|
+
// False means a copy PPM did not install — the host's own, which is not PPM's to remove.
|
|
172
|
+
const removed = uninstallMediamtx();
|
|
173
|
+
return removed
|
|
174
|
+
? c.json(ok({ removed }))
|
|
175
|
+
: c.json(err("this copy was not installed by PPM"), 409);
|
|
176
|
+
} catch (e) {
|
|
177
|
+
return c.json(err((e as Error).message), 409);
|
|
178
|
+
}
|
|
179
|
+
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session trace over HTTP: browser logs coming in, and a session's timeline going out.
|
|
3
|
+
* Both sit behind authMiddleware — an error raised while logged out is lost, by decision.
|
|
4
|
+
*/
|
|
5
|
+
import { Hono } from "hono";
|
|
6
|
+
import { ok, err } from "../../types/api.ts";
|
|
7
|
+
import {
|
|
8
|
+
DeviceRateLimiter,
|
|
9
|
+
TRACE_INGEST_MAX_BODY_BYTES,
|
|
10
|
+
browserRows,
|
|
11
|
+
parseBrowserBatch,
|
|
12
|
+
} from "../../services/session-trace/browser-trace-ingest.ts";
|
|
13
|
+
import { appendBatch, readSessionTimeline } from "../../services/session-trace/session-trace-store.ts";
|
|
14
|
+
|
|
15
|
+
const MAX_TIMELINE_ROWS = 20_000;
|
|
16
|
+
|
|
17
|
+
export function createTraceRoutes(limiter = new DeviceRateLimiter()): Hono {
|
|
18
|
+
const routes = new Hono();
|
|
19
|
+
|
|
20
|
+
/** POST /api/trace — body: BrowserTraceBatch (`src/shared/session-trace.ts`). */
|
|
21
|
+
routes.post("/", async (c) => {
|
|
22
|
+
const declared = Number(c.req.header("content-length") ?? 0);
|
|
23
|
+
if (declared > TRACE_INGEST_MAX_BODY_BYTES) return c.json(err("batch too large"), 400);
|
|
24
|
+
let text: string;
|
|
25
|
+
try { text = await c.req.text(); } catch { return c.json(err("unreadable body"), 400); }
|
|
26
|
+
if (Buffer.byteLength(text) > TRACE_INGEST_MAX_BODY_BYTES) return c.json(err("batch too large"), 400);
|
|
27
|
+
let raw: unknown;
|
|
28
|
+
try { raw = JSON.parse(text); } catch { return c.json(err("body must be JSON"), 400); }
|
|
29
|
+
|
|
30
|
+
const parsed = parseBrowserBatch(raw);
|
|
31
|
+
if (!parsed.ok) return c.json(err(parsed.error), 400);
|
|
32
|
+
|
|
33
|
+
const { batch, dropped } = parsed;
|
|
34
|
+
const verdict = limiter.take(batch.deviceId, batch.entries.length);
|
|
35
|
+
if (!verdict.allowed) {
|
|
36
|
+
c.header("Retry-After", String(Math.ceil(verdict.retryAfterMs / 1000)));
|
|
37
|
+
return c.json(err("too many trace entries from this device"), 429);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
appendBatch(browserRows(batch));
|
|
42
|
+
} catch (e) {
|
|
43
|
+
// 503 rather than 500: the client keeps the batch and tries again later.
|
|
44
|
+
console.warn(`[session-trace] browser batch not stored: ${(e as Error).message}`);
|
|
45
|
+
return c.json(err("trace store unavailable"), 503);
|
|
46
|
+
}
|
|
47
|
+
return c.json(ok({ accepted: batch.entries.length, dropped }));
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* GET /api/trace/sessions/:sessionId — the session's own trace plus every browser row filed
|
|
52
|
+
* against it, under any id the session has had, oldest first.
|
|
53
|
+
*/
|
|
54
|
+
routes.get("/sessions/:sessionId", (c) => {
|
|
55
|
+
const limit = Math.min(MAX_TIMELINE_ROWS, Math.max(1, Number(c.req.query("limit")) || 5_000));
|
|
56
|
+
try {
|
|
57
|
+
return c.json(ok({ events: readSessionTimeline(c.req.param("sessionId"), limit) }));
|
|
58
|
+
} catch (e) {
|
|
59
|
+
return c.json(err((e as Error).message), 500);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
return routes;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export const traceRoutes = createTraceRoutes();
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WS handler for `/ws/android` — binary out = framed H.264 access units, text in/out = JSON.
|
|
3
|
+
*
|
|
4
|
+
* The upgrade succeeding proves nothing: the coarse PPM token travels as `?token=` on every WS
|
|
5
|
+
* URL (`isWsUpgradeAuthorized`, `src/server/index.ts`) and says only "holds the one reusable app
|
|
6
|
+
* token". A socket that hands over touch and keyboard control of a running VM therefore requires
|
|
7
|
+
* a single-use, short-TTL nonce from `POST /api/android/devices/:deviceId/sessions` as the
|
|
8
|
+
* client's *first* message, and the nonce — not the URL — is what names the device.
|
|
9
|
+
*
|
|
10
|
+
* The flag and `auth.enabled` are re-checked here even though `src/server/index.ts` already gated
|
|
11
|
+
* the upgrade on them: this handler must never depend on one call site staying correct, and must
|
|
12
|
+
* NOT reuse `isWsUpgradeAuthorized()`, which returns true unconditionally when PPM auth is off.
|
|
13
|
+
*/
|
|
14
|
+
import { configService } from "../../services/config.service.ts";
|
|
15
|
+
import { isAndroidEmulatorEnabled } from "../../services/android/android-flag.ts";
|
|
16
|
+
import { consumeAndroidNonce } from "../../services/android/android-nonce.ts";
|
|
17
|
+
import { findRunningByDeviceId } from "../../services/android/device-registry.ts";
|
|
18
|
+
import {
|
|
19
|
+
attachAndroidViewer, registerAndroidExitSweep,
|
|
20
|
+
type AndroidSocket, type AndroidViewerSession,
|
|
21
|
+
} from "../../services/android/android-session.ts";
|
|
22
|
+
import { ANDROID_QUALITY_PRESETS, type AndroidQuality } from "../../shared/android-protocol.ts";
|
|
23
|
+
|
|
24
|
+
registerAndroidExitSweep();
|
|
25
|
+
|
|
26
|
+
interface AndroidWs {
|
|
27
|
+
data: { type: "android"; session?: AndroidViewerSession; authenticated?: boolean; closed?: boolean };
|
|
28
|
+
send: (d: string | Uint8Array) => number;
|
|
29
|
+
getBufferedAmount?: () => number;
|
|
30
|
+
close: (code?: number, reason?: string) => void;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function guardOrClose(ws: AndroidWs): boolean {
|
|
34
|
+
if (!isAndroidEmulatorEnabled()) { ws.close(1008, "android emulator support is disabled"); return false; }
|
|
35
|
+
if (!configService.get("auth").enabled) {
|
|
36
|
+
ws.close(1008, "android emulator control requires PPM authentication to be enabled");
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** The session layer's view of this socket. `isClosed` reads the flag `close` below sets: a
|
|
43
|
+
* close during `attachAndroidViewer` finds no session to close yet. */
|
|
44
|
+
export function androidSocketFor(ws: AndroidWs): AndroidSocket {
|
|
45
|
+
return {
|
|
46
|
+
send: (d) => ws.send(d),
|
|
47
|
+
getBufferedAmount: ws.getBufferedAmount ? () => ws.getBufferedAmount!() : undefined,
|
|
48
|
+
close: (code, reason) => ws.close(code, reason),
|
|
49
|
+
isClosed: () => ws.data.closed === true,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function parseQuality(value: unknown): AndroidQuality {
|
|
54
|
+
return typeof value === "string" && value in ANDROID_QUALITY_PRESETS ? (value as AndroidQuality) : "balanced";
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function authenticateFirstMessage(ws: AndroidWs, text: string): Promise<void> {
|
|
58
|
+
let parsed: Record<string, unknown>;
|
|
59
|
+
try { parsed = JSON.parse(text); } catch { ws.close(1008, "expected auth message"); return; }
|
|
60
|
+
|
|
61
|
+
const nonce = parsed.type === "auth" ? parsed.nonce : undefined;
|
|
62
|
+
const deviceId = typeof nonce === "string" ? consumeAndroidNonce(nonce) : null;
|
|
63
|
+
if (!deviceId) { ws.close(1008, "invalid or expired session nonce"); return; }
|
|
64
|
+
|
|
65
|
+
const emulator = findRunningByDeviceId(deviceId);
|
|
66
|
+
if (!emulator) { ws.close(1011, "that emulator is no longer running"); return; }
|
|
67
|
+
|
|
68
|
+
ws.data.authenticated = true;
|
|
69
|
+
try {
|
|
70
|
+
ws.data.session = await attachAndroidViewer(androidSocketFor(ws), {
|
|
71
|
+
deviceId,
|
|
72
|
+
emulator,
|
|
73
|
+
quality: parseQuality(parsed.quality),
|
|
74
|
+
});
|
|
75
|
+
} catch (e) {
|
|
76
|
+
console.error(`[android] failed to start a session: ${(e as Error).message}`);
|
|
77
|
+
ws.send(JSON.stringify({ type: "error", message: (e as Error).message, fatal: true }));
|
|
78
|
+
ws.close(1011, "session failed to start");
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export const androidWebSocket = {
|
|
83
|
+
open(ws: AndroidWs) {
|
|
84
|
+
guardOrClose(ws); // nothing else until the client's first (auth) message arrives
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
async message(ws: AndroidWs, msg: string | ArrayBuffer | Uint8Array) {
|
|
88
|
+
if (!guardOrClose(ws)) return;
|
|
89
|
+
const text = typeof msg === "string" ? msg : new TextDecoder().decode(msg as ArrayBuffer);
|
|
90
|
+
if (!ws.data.authenticated) {
|
|
91
|
+
await authenticateFirstMessage(ws, text);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
await ws.data.session?.handleClientMessage(text);
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
close(ws: AndroidWs) {
|
|
98
|
+
ws.data.closed = true;
|
|
99
|
+
ws.data.session?.close();
|
|
100
|
+
},
|
|
101
|
+
};
|
package/src/server/ws/chat.ts
CHANGED
|
@@ -180,7 +180,7 @@ function releaseSubprocess(sessionId: string, reason: string, note: string): voi
|
|
|
180
180
|
if (entry.clients.size > 0 || entry.isStreamingActive) return;
|
|
181
181
|
const provider = providerRegistry.get(entry.providerId);
|
|
182
182
|
if (!provider?.hasStreamingSession?.(sessionId)) return;
|
|
183
|
-
|
|
183
|
+
chatService.abortQuery(entry.providerId, sessionId, reason, "ws");
|
|
184
184
|
console.log(`[chat] session=${sessionId} released subprocess (${reason})`);
|
|
185
185
|
logSessionEvent(sessionId, "INFO", note);
|
|
186
186
|
}
|
|
@@ -204,7 +204,7 @@ export function dropIdleSubprocess(sessionId: string, reason: string, note: stri
|
|
|
204
204
|
if (!entry) return;
|
|
205
205
|
const provider = providerRegistry.get(entry.providerId);
|
|
206
206
|
if (!provider?.hasStreamingSession?.(sessionId)) return;
|
|
207
|
-
|
|
207
|
+
chatService.abortQuery(entry.providerId, sessionId, reason, "ws");
|
|
208
208
|
if (entry.cacheReleaseTimer) {
|
|
209
209
|
clearTimeout(entry.cacheReleaseTimer);
|
|
210
210
|
entry.cacheReleaseTimer = undefined;
|
|
@@ -612,7 +612,7 @@ function startCleanupTimer(sessionId: string): void {
|
|
|
612
612
|
// flight, so the session entry going away is the last chance to free the process.
|
|
613
613
|
const provider = providerRegistry.get(entry.providerId);
|
|
614
614
|
if (provider?.hasStreamingSession?.(sessionId)) {
|
|
615
|
-
|
|
615
|
+
chatService.abortQuery(entry.providerId, sessionId, "idle_timeout", "ws");
|
|
616
616
|
}
|
|
617
617
|
if (entry.cacheReleaseTimer) {
|
|
618
618
|
clearTimeout(entry.cacheReleaseTimer);
|
|
@@ -696,7 +696,7 @@ async function startSessionConsumer(sessionId: string, providerId: string, conte
|
|
|
696
696
|
// provider config: omit so the provider falls back. thinking 0 = explicit OFF (overrides config).
|
|
697
697
|
const effortOverride = getSessionEffort(sessionId) ?? undefined;
|
|
698
698
|
const thinkingBudget = getSessionThinking(sessionId);
|
|
699
|
-
for await (const event of chatService.sendMessage(providerId, sessionId, content, { permissionMode, images, ...(imagePaths?.length && { imagePaths }), ...(model && { model }), ...(effortOverride && { effort: effortOverride }), ...(thinkingBudget != null && { thinkingBudget }) })) {
|
|
699
|
+
for await (const event of chatService.sendMessage(providerId, sessionId, content, { permissionMode, images, ...(imagePaths?.length && { imagePaths }), ...(model && { model }), ...(effortOverride && { effort: effortOverride }), ...(thinkingBudget != null && { thinkingBudget }), origin: "ws" })) {
|
|
700
700
|
eventCount++;
|
|
701
701
|
const ev = event as any;
|
|
702
702
|
const evType = ev.type ?? "unknown";
|
|
@@ -1388,9 +1388,7 @@ export const chatWebSocket = {
|
|
|
1388
1388
|
// unblock it, then the follow-up message flows through normally.
|
|
1389
1389
|
if (entry.pendingApprovalEvent) {
|
|
1390
1390
|
const pendingReqId = entry.pendingApprovalEvent.requestId;
|
|
1391
|
-
|
|
1392
|
-
provider.resolveApproval(pendingReqId, false);
|
|
1393
|
-
}
|
|
1391
|
+
chatService.resolveApproval(providerId, sessionId, pendingReqId, false, undefined, { reason: "superseded_by_message", origin: "ws" });
|
|
1394
1392
|
entry.pendingApprovalEvent = undefined;
|
|
1395
1393
|
broadcast(sessionId, {
|
|
1396
1394
|
type: "approval_resolved",
|
|
@@ -1442,6 +1440,7 @@ export const chatWebSocket = {
|
|
|
1442
1440
|
const effort = getSessionEffort(sessionId) ?? undefined;
|
|
1443
1441
|
const thinkingBudget = getSessionThinking(sessionId);
|
|
1444
1442
|
await chatService.pushMessage(providerId, sessionId, parsed.content, {
|
|
1443
|
+
origin: "ws",
|
|
1445
1444
|
priority: parsed.priority ?? 'next',
|
|
1446
1445
|
images: parsed.images,
|
|
1447
1446
|
imagePaths: parsed.imagePaths,
|
|
@@ -1472,7 +1471,7 @@ export const chatWebSocket = {
|
|
|
1472
1471
|
// Aborting the idle-but-alive subprocess forces the next message to take
|
|
1473
1472
|
// the resume path, recreating the query with the new model.
|
|
1474
1473
|
if (hasLiveStream && entry.phase === "idle") {
|
|
1475
|
-
|
|
1474
|
+
chatService.abortQuery(providerId, sessionId, "set_model", "ws");
|
|
1476
1475
|
}
|
|
1477
1476
|
logSessionEvent(sessionId, "INFO", `Model switched to ${parsed.model}`);
|
|
1478
1477
|
ws.send(JSON.stringify({
|
|
@@ -1499,7 +1498,7 @@ export const chatWebSocket = {
|
|
|
1499
1498
|
// Abort only when idle-but-alive so the next turn recreates the query with the new
|
|
1500
1499
|
// effort (mirror set_model); never interrupt an active turn.
|
|
1501
1500
|
if ((provider?.hasStreamingSession?.(sessionId) ?? false) && entry.phase === "idle") {
|
|
1502
|
-
|
|
1501
|
+
chatService.abortQuery(providerId, sessionId, "set_effort", "ws");
|
|
1503
1502
|
}
|
|
1504
1503
|
logSessionEvent(sessionId, "INFO", `Effort switched to ${parsed.effort}`);
|
|
1505
1504
|
ws.send(JSON.stringify({
|
|
@@ -1520,7 +1519,7 @@ export const chatWebSocket = {
|
|
|
1520
1519
|
setSessionThinking(sessionId, parsed.enabled ? THINKING_ADAPTIVE : 0);
|
|
1521
1520
|
const provider = providerRegistry.get(providerId);
|
|
1522
1521
|
if ((provider?.hasStreamingSession?.(sessionId) ?? false) && entry.phase === "idle") {
|
|
1523
|
-
|
|
1522
|
+
chatService.abortQuery(providerId, sessionId, "set_thinking", "ws");
|
|
1524
1523
|
}
|
|
1525
1524
|
logSessionEvent(sessionId, "INFO", `Thinking ${parsed.enabled ? "on" : "off"}`);
|
|
1526
1525
|
ws.send(JSON.stringify({
|
|
@@ -1537,11 +1536,10 @@ export const chatWebSocket = {
|
|
|
1537
1536
|
}));
|
|
1538
1537
|
} else if (parsed.type === "cancel") {
|
|
1539
1538
|
// Fully teardown streaming session — user must resume to continue
|
|
1540
|
-
const provider = providerRegistry.get(providerId);
|
|
1541
1539
|
const phase = entry?.phase ?? "unknown";
|
|
1542
1540
|
console.log(`[chat] session=${sessionId} WS cancel received from FE (phase=${phase})`);
|
|
1543
1541
|
logSessionEvent(sessionId, "CANCEL", `WS cancel from FE (phase=${phase})`);
|
|
1544
|
-
|
|
1542
|
+
chatService.abortQuery(providerId, sessionId, "ws_cancel", "ws");
|
|
1545
1543
|
} else if (parsed.type === "kill_background_shell") {
|
|
1546
1544
|
// Kill via the AI: enqueue an instruction so the model calls KillShell.
|
|
1547
1545
|
// Cross-platform and safe (no OS-PID guessing). Runs when the AI is idle
|
|
@@ -1572,17 +1570,14 @@ export const chatWebSocket = {
|
|
|
1572
1570
|
}, 0);
|
|
1573
1571
|
});
|
|
1574
1572
|
} else if (provider && "pushMessage" in provider) {
|
|
1575
|
-
await chatService.pushMessage(providerId, sessionId, instruction, { priority: "next" });
|
|
1573
|
+
await chatService.pushMessage(providerId, sessionId, instruction, { priority: "next", origin: "ws" });
|
|
1576
1574
|
entry.turnEvents = [];
|
|
1577
1575
|
entry.pendingApprovalEvent = undefined;
|
|
1578
1576
|
setPhase(sessionId, "thinking");
|
|
1579
1577
|
}
|
|
1580
1578
|
logSessionEvent(sessionId, "INFO", `kill_background_shell requested shellId=${shellId}`);
|
|
1581
1579
|
} else if (parsed.type === "approval_response") {
|
|
1582
|
-
|
|
1583
|
-
if (provider && typeof provider.resolveApproval === "function") {
|
|
1584
|
-
provider.resolveApproval(parsed.requestId, parsed.approved, (parsed as any).data);
|
|
1585
|
-
}
|
|
1580
|
+
chatService.resolveApproval(providerId, sessionId, parsed.requestId, parsed.approved, (parsed as any).data, { origin: "ws" });
|
|
1586
1581
|
if (entry) {
|
|
1587
1582
|
entry.pendingApprovalEvent = undefined;
|
|
1588
1583
|
// Enrich the buffered approval_request with response data so replayed
|
package/src/server/ws/global.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* so a workspace whose visible tabs are an editor and a terminal would get no
|
|
10
10
|
* file watching at all — silently breaking editor live-reload, docx/pdf
|
|
11
11
|
* preview reload, and file-tree invalidation.
|
|
12
|
-
* 2. **Cross-cutting broadcasts** (`file:changed`, `session:unread_changed`,
|
|
12
|
+
* 2. **Cross-cutting broadcasts** (`file:changed`, `files:index-changed`, `session:unread_changed`,
|
|
13
13
|
* `session:phase_changed`, `jira:*`, `design:*`). These are app-wide, not session-scoped,
|
|
14
14
|
* so they belong on an app-wide channel.
|
|
15
15
|
*
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
* example, make an editor re-fetch its file twice per change).
|
|
19
19
|
*/
|
|
20
20
|
import { startWatching, stopWatching, onFileChange } from "../../services/file-watcher.service.ts";
|
|
21
|
+
import { onIndexRebuilt, warmIndex } from "../../services/file-list-index.service.ts";
|
|
21
22
|
import { configService } from "../../services/config.service.ts";
|
|
22
23
|
import { onDesignEvent } from "../../services/design/design-events.ts";
|
|
23
24
|
import { resolve } from "node:path";
|
|
@@ -71,6 +72,8 @@ function setWatch(ws: GlobalWsSocket, projectName: string): void {
|
|
|
71
72
|
if (!project) return;
|
|
72
73
|
|
|
73
74
|
void startWatching(projectName, project.path);
|
|
75
|
+
// A client opened this project: have its file list ready before the palette asks for it.
|
|
76
|
+
warmIndex(project.path);
|
|
74
77
|
watchedProject.set(ws, projectName);
|
|
75
78
|
}
|
|
76
79
|
|
|
@@ -86,6 +89,15 @@ function projectNameForPath(projectPath: string): string | null {
|
|
|
86
89
|
return configService.get("projects").find((p) => fold(p.path) === target)?.name ?? null;
|
|
87
90
|
}
|
|
88
91
|
|
|
92
|
+
// A project's file index was rebuilt behind a stale one and now lists other paths. Clients do
|
|
93
|
+
// not refetch the index on every `file:changed` (on a large project that is a 22 MB download
|
|
94
|
+
// per change); this is what tells them the list they hold is out of date.
|
|
95
|
+
onIndexRebuilt((projectPath, changed) => {
|
|
96
|
+
if (!changed) return;
|
|
97
|
+
const projectName = projectNameForPath(projectPath);
|
|
98
|
+
if (projectName) broadcastGlobalEvent({ type: "files:index-changed", projectName });
|
|
99
|
+
});
|
|
100
|
+
|
|
89
101
|
// Design history/comment changes: `.design/` is not watched, so these are the only signal.
|
|
90
102
|
// Services know the project path; browsers address projects by name, and a path no
|
|
91
103
|
// registered project owns is dropped rather than broadcast.
|
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
* client's *first* message — `{type:"auth", nonce, displayId?, cursor?, codec?}` — before
|
|
10
10
|
* capture or input starts. `displayId` picks one of `/capabilities`' `displays`; absent/unknown = primary.
|
|
11
11
|
* `cursor` and `codec` carry the client's saved cursor and encoder prefs, because ffmpeg takes
|
|
12
|
-
* both at startup and applying them afterwards would respawn it on every connect.
|
|
12
|
+
* both at startup and applying them afterwards would respawn it on every connect. `webrtc`
|
|
13
|
+
* asks for the relay transport; when it is not installed the session answers on this socket
|
|
14
|
+
* as before rather than failing. Kept out of
|
|
13
15
|
* the query string (unlike the coarse token) so they never land in proxy/tunnel access logs.
|
|
14
16
|
*
|
|
15
17
|
* Both the feature flag and `auth.enabled` are re-checked here even though
|
|
@@ -66,6 +68,9 @@ async function authenticateFirstMessage(ws: RemoteDesktopWs, text: string): Prom
|
|
|
66
68
|
// getting the pointer it has always had.
|
|
67
69
|
showCursor: parsed.cursor !== false,
|
|
68
70
|
encoder: typeof parsed.codec === "string" ? parsed.codec : undefined,
|
|
71
|
+
// Opt-in, and only honoured when the relay is installed: an older client sends no flag
|
|
72
|
+
// and keeps the WebSocket path it has always used.
|
|
73
|
+
webrtc: parsed.webrtc === true,
|
|
69
74
|
});
|
|
70
75
|
} catch (e) {
|
|
71
76
|
console.error(`[remote-desktop] failed to start capture: ${(e as Error).message}`);
|