@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
|
@@ -2,46 +2,120 @@
|
|
|
2
2
|
* file-list-index.service.ts
|
|
3
3
|
* Lazy-load file tree listing and flat index building for palette/search.
|
|
4
4
|
* Implements listDir() (1-level) and buildIndex() (recursive) with filter support.
|
|
5
|
-
* Index results are cached per project path
|
|
5
|
+
* Index results are cached per project path; a file change marks them stale (see IndexEntry).
|
|
6
|
+
* The walk itself runs on the file index worker (file-index/), off the thread serving requests.
|
|
6
7
|
*/
|
|
7
8
|
|
|
8
|
-
import { existsSync, readdirSync
|
|
9
|
-
import { resolve,
|
|
10
|
-
import
|
|
11
|
-
import type {
|
|
9
|
+
import { existsSync, readdirSync } from "node:fs";
|
|
10
|
+
import { resolve, sep } from "node:path";
|
|
11
|
+
import type { Ignore } from "ignore";
|
|
12
|
+
import type { FileDirEntry, FileEntry } from "../types/project.ts";
|
|
12
13
|
import { matchesGlob, resolveFilter } from "./file-filter.service.ts";
|
|
13
14
|
import { SecurityError, NotFoundError } from "./file.service.ts";
|
|
15
|
+
import { loadGitignore, type IndexBuild } from "./file-index/index-walk.ts";
|
|
16
|
+
import { fileIndexRunner } from "./file-index/file-index-runner.ts";
|
|
17
|
+
import type { SearchKind } from "./file-index/index-search.ts";
|
|
14
18
|
|
|
15
19
|
// ---------------------------------------------------------------------------
|
|
16
20
|
// Index cache keyed by absolute project path
|
|
17
21
|
// ---------------------------------------------------------------------------
|
|
18
22
|
|
|
19
|
-
|
|
23
|
+
/**
|
|
24
|
+
* One project's index. A file change marks it `stale` instead of dropping it, and the list is
|
|
25
|
+
* walked again in the background a second later (see `refreshDelay`), so the palette opens on a
|
|
26
|
+
* list that is already current. A request that finds it stale is answered with the rebuild if
|
|
27
|
+
* that lands within `REBUILD_GRACE_MS`, and with the list already held otherwise. It used to be
|
|
28
|
+
* dropped, so the explorer's refetch after every change walked the project on the request
|
|
29
|
+
* itself — nxsys-workspace (181k entries) took 6.2 s per walk, and a session writing test
|
|
30
|
+
* artefacts into it kept the server frozen until the supervisor killed it.
|
|
31
|
+
*/
|
|
32
|
+
interface IndexEntry {
|
|
33
|
+
build: IndexBuild | null;
|
|
34
|
+
/** Something under the project changed since `build` was walked. */
|
|
35
|
+
stale: boolean;
|
|
36
|
+
/** The walk under way, shared by every request that arrives while it runs. */
|
|
37
|
+
building: Promise<IndexBuild> | null;
|
|
38
|
+
/**
|
|
39
|
+
* A request found a change that landed after the running walk began, which that walk may
|
|
40
|
+
* have passed by — so walk once more as soon as it ends, instead of after `refreshDelay`.
|
|
41
|
+
* Only for a project quick to walk (`QUICK_WALK_MS`); a slow one keeps to its schedule.
|
|
42
|
+
*/
|
|
43
|
+
rewalk: boolean;
|
|
44
|
+
/** The background walk a change scheduled. */
|
|
45
|
+
refreshTimer: ReturnType<typeof setTimeout> | null;
|
|
46
|
+
/** When the last walk ended, and how long it took: what spaces background walks out. */
|
|
47
|
+
lastWalkEndedAt: number;
|
|
48
|
+
lastWalkMs: number;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const indexCache = new Map<string, IndexEntry>();
|
|
52
|
+
|
|
53
|
+
type RebuiltListener = (projectPath: string, changed: boolean) => void;
|
|
54
|
+
const rebuiltListeners = new Set<RebuiltListener>();
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Called after each background rebuild — one that replaced a list already being served — with
|
|
58
|
+
* whether its paths differ. Content-only churn (a log being appended to) rebuilds to the same
|
|
59
|
+
* list, and nothing downstream needs to hear about that.
|
|
60
|
+
*/
|
|
61
|
+
export function onIndexRebuilt(listener: RebuiltListener): () => void {
|
|
62
|
+
rebuiltListeners.add(listener);
|
|
63
|
+
return () => { rebuiltListeners.delete(listener); };
|
|
64
|
+
}
|
|
20
65
|
|
|
21
|
-
/**
|
|
66
|
+
/**
|
|
67
|
+
* Drop a project's index, so the next request waits for a fresh walk. For filter changes: the
|
|
68
|
+
* list held was built with the old filters, so it is wrong rather than merely old.
|
|
69
|
+
*/
|
|
22
70
|
export function invalidateIndexCache(projectPath: string): void {
|
|
71
|
+
const entry = indexCache.get(projectPath);
|
|
72
|
+
if (entry?.refreshTimer) clearTimeout(entry.refreshTimer);
|
|
23
73
|
indexCache.delete(projectPath);
|
|
24
74
|
}
|
|
25
75
|
|
|
76
|
+
/** A file changed: keep serving the index held, and walk it again in the background. */
|
|
77
|
+
export function markIndexStale(projectPath: string): void {
|
|
78
|
+
const entry = indexCache.get(projectPath);
|
|
79
|
+
if (!entry) return;
|
|
80
|
+
entry.stale = true;
|
|
81
|
+
scheduleRefresh(projectPath, entry);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Walk a project nobody has asked about yet, so its first palette opens on a ready list. The
|
|
86
|
+
* walk runs on the index worker; a failure is left to the first request to report.
|
|
87
|
+
*/
|
|
88
|
+
export function warmIndex(projectPath: string): void {
|
|
89
|
+
if (indexCache.has(projectPath)) return;
|
|
90
|
+
buildIndex(projectPath).catch(() => {});
|
|
91
|
+
}
|
|
92
|
+
|
|
26
93
|
/** Clear all cached indexes (e.g. for tests) */
|
|
27
94
|
export function clearIndexCache(): void {
|
|
95
|
+
for (const entry of indexCache.values()) {
|
|
96
|
+
if (entry.refreshTimer) clearTimeout(entry.refreshTimer);
|
|
97
|
+
}
|
|
28
98
|
indexCache.clear();
|
|
29
99
|
}
|
|
30
100
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
101
|
+
/**
|
|
102
|
+
* How long after a change to walk again. A second, so a burst of writes costs one walk — but no
|
|
103
|
+
* sooner after the last walk ended than that walk took, so a project as slow as nxsys-workspace
|
|
104
|
+
* (~7 s) keeps the worker busy at most half the time while a session writes into it without end.
|
|
105
|
+
*/
|
|
106
|
+
export function refreshDelay(now: number, lastWalkEndedAt: number, lastWalkMs: number): number {
|
|
107
|
+
return Math.max(REFRESH_AFTER_CHANGE_MS, lastWalkEndedAt + lastWalkMs - now);
|
|
108
|
+
}
|
|
34
109
|
|
|
35
|
-
function
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if (
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
return ig;
|
|
110
|
+
function scheduleRefresh(projectPath: string, entry: IndexEntry): void {
|
|
111
|
+
// With no list yet the first request walks, and a walk under way reschedules when it ends.
|
|
112
|
+
// Starting a walk and dropping the entry both clear the timer, so one that fires has work to do.
|
|
113
|
+
if (entry.refreshTimer || !entry.build || entry.building) return;
|
|
114
|
+
entry.refreshTimer = setTimeout(() => {
|
|
115
|
+
entry.refreshTimer = null;
|
|
116
|
+
startBuild(projectPath, entry);
|
|
117
|
+
}, refreshDelay(Date.now(), entry.lastWalkEndedAt, entry.lastWalkMs));
|
|
118
|
+
entry.refreshTimer.unref?.();
|
|
45
119
|
}
|
|
46
120
|
|
|
47
121
|
// ---------------------------------------------------------------------------
|
|
@@ -146,59 +220,104 @@ function listDirWithContext(projectPath: string, relPath: string, { filter, ig }
|
|
|
146
220
|
// ---------------------------------------------------------------------------
|
|
147
221
|
|
|
148
222
|
/**
|
|
149
|
-
*
|
|
223
|
+
* How long a request for a stale index waits for the rebuild before it is answered with the
|
|
224
|
+
* list already held. Most projects are walked well inside it — PPM's own 3.2k entries take
|
|
225
|
+
* ~80 ms — so a file just created is in the palette the first time it opens. nxsys-workspace
|
|
226
|
+
* (181k entries, ~7 s) is answered with the list it had, and `files:index-changed` follows.
|
|
227
|
+
*/
|
|
228
|
+
const REBUILD_GRACE_MS = 1000;
|
|
229
|
+
|
|
230
|
+
/** See `refreshDelay`. */
|
|
231
|
+
const REFRESH_AFTER_CHANGE_MS = 1000;
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* The slowest walk a request waits for. A slower project is answered with the list it has at
|
|
235
|
+
* once and rebuilt on its own schedule, and whatever is showing the list gets the rebuild when
|
|
236
|
+
* `files:index-changed` says it landed. Measured on a 186k-entry project written into every
|
|
237
|
+
* 50 ms: waiting for its ~1 s walks made every palette open take 969 ms (p50), and walking again
|
|
238
|
+
* straight after each walk that a request found stale kept the worker walking without a break.
|
|
239
|
+
*/
|
|
240
|
+
const QUICK_WALK_MS = 250;
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Flat index of all files in the project for palette/search, as the `/files/index` response.
|
|
150
244
|
* Applies filesExclude + searchExclude + optional gitignore.
|
|
151
|
-
*
|
|
245
|
+
*
|
|
246
|
+
* Only a project with no list yet waits for the whole walk. A stale list of a project that is
|
|
247
|
+
* quick to walk is replaced if its rebuild lands within `graceMs`; any other stale list is
|
|
248
|
+
* returned as it is, the rebuild carrying on behind it — see `IndexEntry`. `graceMs` 0 answers
|
|
249
|
+
* with the held list at once.
|
|
152
250
|
*/
|
|
153
|
-
export function buildIndex(projectPath: string):
|
|
154
|
-
|
|
155
|
-
if (
|
|
251
|
+
export function buildIndex(projectPath: string, graceMs = REBUILD_GRACE_MS): Promise<IndexBuild> {
|
|
252
|
+
let entry = indexCache.get(projectPath);
|
|
253
|
+
if (!entry) {
|
|
254
|
+
entry = { build: null, stale: true, building: null, rewalk: false, refreshTimer: null, lastWalkEndedAt: 0, lastWalkMs: 0 };
|
|
255
|
+
indexCache.set(projectPath, entry);
|
|
256
|
+
}
|
|
257
|
+
const held = entry.build;
|
|
258
|
+
const waits = !held || entry.lastWalkMs <= QUICK_WALK_MS;
|
|
259
|
+
if (entry.stale) {
|
|
260
|
+
// A slow list is refreshed on its schedule; this only re-arms one a failed walk left unset.
|
|
261
|
+
if (!waits) scheduleRefresh(projectPath, entry);
|
|
262
|
+
else if (entry.building) entry.rewalk = true;
|
|
263
|
+
else startBuild(projectPath, entry);
|
|
264
|
+
}
|
|
265
|
+
const building = entry.building;
|
|
266
|
+
if (!building) return Promise.resolve(held!);
|
|
267
|
+
if (!held) return building;
|
|
268
|
+
if (graceMs <= 0 || !waits) return Promise.resolve(held);
|
|
269
|
+
const graceOver = new Promise<IndexBuild>((resolve) => setTimeout(() => resolve(held), graceMs));
|
|
270
|
+
return Promise.race([building.catch(() => held), graceOver]);
|
|
271
|
+
}
|
|
156
272
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
return
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function walkForIndex(
|
|
169
|
-
rootPath: string,
|
|
170
|
-
dirPath: string,
|
|
171
|
-
allExclude: string[],
|
|
172
|
-
ig: Ignore | null,
|
|
173
|
-
results: FileEntry[],
|
|
174
|
-
): void {
|
|
175
|
-
let dirEntries;
|
|
176
|
-
try { dirEntries = readdirSync(dirPath, { withFileTypes: true }); }
|
|
177
|
-
catch { return; }
|
|
178
|
-
|
|
179
|
-
for (const entry of dirEntries) {
|
|
180
|
-
const fullPath = join(dirPath, entry.name);
|
|
181
|
-
const relPath = relative(rootPath, fullPath);
|
|
182
|
-
const relPosix = relPath.split("\\").join("/");
|
|
183
|
-
|
|
184
|
-
// Apply glob exclusion (check full relative path and bare entry name)
|
|
185
|
-
// These are HARD excludes — .git, node_modules, dist, etc.
|
|
186
|
-
if (matchesGlob(relPosix, allExclude)) continue;
|
|
187
|
-
if (matchesGlob(entry.name, allExclude)) continue;
|
|
188
|
-
|
|
189
|
-
// Apply gitignore rules — SOFT exclude only (mark with isIgnored flag).
|
|
190
|
-
// Huge dirs like node_modules/dist/build are already hard-excluded by glob above.
|
|
191
|
-
let isIgnored = false;
|
|
192
|
-
if (ig) {
|
|
193
|
-
const checkPath = entry.isDirectory() ? `${relPosix}/` : relPosix;
|
|
194
|
-
isIgnored = ig.ignores(checkPath) || ig.ignores(relPosix);
|
|
195
|
-
}
|
|
273
|
+
/**
|
|
274
|
+
* The best `limit` entries of the project's list for `query`, best first, searched where the list
|
|
275
|
+
* is held (see `index-search.ts`) — for a project whose list is too long to send to a browser.
|
|
276
|
+
* Searches the list `buildIndex` would answer with, and never waits for a rebuild: this is asked
|
|
277
|
+
* once per keystroke.
|
|
278
|
+
*/
|
|
279
|
+
export async function searchFileIndex(projectPath: string, query: string, kind: SearchKind, limit: number): Promise<FileEntry[]> {
|
|
280
|
+
const build = await buildIndex(projectPath, 0);
|
|
281
|
+
return fileIndexRunner.search(projectPath, build, query, kind, limit);
|
|
282
|
+
}
|
|
196
283
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
284
|
+
function startBuild(projectPath: string, entry: IndexEntry): Promise<IndexBuild> {
|
|
285
|
+
const previous = entry.build;
|
|
286
|
+
if (entry.refreshTimer) {
|
|
287
|
+
clearTimeout(entry.refreshTimer);
|
|
288
|
+
entry.refreshTimer = null;
|
|
289
|
+
}
|
|
290
|
+
// A change from here on may land behind the walk, so it has to leave the list stale again.
|
|
291
|
+
entry.stale = false;
|
|
292
|
+
entry.rewalk = false;
|
|
293
|
+
const filter = resolveFilter(projectPath);
|
|
294
|
+
const build = fileIndexRunner.run(projectPath, {
|
|
295
|
+
exclude: [...filter.filesExclude, ...filter.searchExclude],
|
|
296
|
+
useIgnoreFiles: filter.useIgnoreFiles,
|
|
297
|
+
}).then(
|
|
298
|
+
(next) => {
|
|
299
|
+
entry.building = null;
|
|
300
|
+
entry.build = next;
|
|
301
|
+
entry.lastWalkEndedAt = Date.now();
|
|
302
|
+
entry.lastWalkMs = next.walkMs;
|
|
303
|
+
if (previous) {
|
|
304
|
+
const changed = previous.hash !== next.hash;
|
|
305
|
+
for (const listener of rebuiltListeners) listener(projectPath, changed);
|
|
306
|
+
}
|
|
307
|
+
if (entry.rewalk) startBuild(projectPath, entry);
|
|
308
|
+
else if (entry.stale) scheduleRefresh(projectPath, entry);
|
|
309
|
+
return next;
|
|
310
|
+
},
|
|
311
|
+
(e) => {
|
|
312
|
+
entry.building = null;
|
|
313
|
+
entry.stale = true;
|
|
314
|
+
throw e;
|
|
315
|
+
},
|
|
316
|
+
);
|
|
317
|
+
entry.building = build;
|
|
318
|
+
// Nobody awaits a background rebuild, so its failure is only logged; the old list stays.
|
|
319
|
+
if (previous) {
|
|
320
|
+
build.catch((e) => console.warn(`[file-index] rebuilding ${projectPath} failed: ${(e as Error).message}`));
|
|
203
321
|
}
|
|
322
|
+
return build;
|
|
204
323
|
}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Directory watches on Linux through one raw inotify instance, called over bun:ffi.
|
|
3
|
+
*
|
|
4
|
+
* Bun's `fs.watch` on Linux opens a descriptor for every *file* in a watched directory as well
|
|
5
|
+
* as for the directory itself (4 directories holding 600 files come to 604). The live server
|
|
6
|
+
* held 76,565 of them for its projects — 63k files, 13k directories — and every `Bun.spawn`
|
|
7
|
+
* then took ~5.6 ms instead of 0.08 ms, so each git call, terminal and Claude session stopped
|
|
8
|
+
* the event loop that long. An inotify watch on a directory already reports every entry in it:
|
|
9
|
+
* here the whole process holds one descriptor, whatever it watches.
|
|
10
|
+
*
|
|
11
|
+
* It also sidesteps Bun's registry being keyed by path, which makes a re-watch of a deleted and
|
|
12
|
+
* recreated directory silent forever (see recreated-dir-poller.ts): a recreated directory is a
|
|
13
|
+
* new inode, and adding a watch for it simply returns a new watch descriptor.
|
|
14
|
+
*
|
|
15
|
+
* The descriptor is read on a timer. `Bun.file(fd).stream()` over it ends at once with no events
|
|
16
|
+
* (measured), and a read that blocks would hold the loop; a non-blocking read that finds nothing
|
|
17
|
+
* is one syscall. The file watcher batches what it reports into 500 ms windows anyway.
|
|
18
|
+
*/
|
|
19
|
+
import { dlopen, FFIType, ptr, type Pointer } from "bun:ffi";
|
|
20
|
+
|
|
21
|
+
const IN_NONBLOCK = 0o4000;
|
|
22
|
+
const IN_CLOEXEC = 0o2000000;
|
|
23
|
+
|
|
24
|
+
const IN_MODIFY = 0x2;
|
|
25
|
+
const IN_ATTRIB = 0x4;
|
|
26
|
+
const IN_MOVED_FROM = 0x40;
|
|
27
|
+
const IN_MOVED_TO = 0x80;
|
|
28
|
+
const IN_CREATE = 0x100;
|
|
29
|
+
const IN_DELETE = 0x200;
|
|
30
|
+
const IN_Q_OVERFLOW = 0x4000;
|
|
31
|
+
/** The kernel dropped the watch: the directory is gone, its filesystem unmounted, or we removed it. */
|
|
32
|
+
const IN_IGNORED = 0x8000;
|
|
33
|
+
const IN_ONLYDIR = 0x01000000;
|
|
34
|
+
const IN_DONT_FOLLOW = 0x02000000;
|
|
35
|
+
|
|
36
|
+
/** The split libuv (and so Node's `fs.watch`) makes: entries appearing or vanishing vs. content. */
|
|
37
|
+
const RENAME_EVENTS = IN_MOVED_FROM | IN_MOVED_TO | IN_CREATE | IN_DELETE;
|
|
38
|
+
const CHANGE_EVENTS = IN_MODIFY | IN_ATTRIB;
|
|
39
|
+
const WATCH_MASK = RENAME_EVENTS | CHANGE_EVENTS | IN_ONLYDIR | IN_DONT_FOLLOW;
|
|
40
|
+
|
|
41
|
+
/** `struct inotify_event`: int wd; uint32 mask, cookie, len; then `len` bytes of NUL-padded name. */
|
|
42
|
+
const EVENT_HEADER_BYTES = 16;
|
|
43
|
+
const READ_BUFFER_BYTES = 64 * 1024;
|
|
44
|
+
const POLL_MS = 50;
|
|
45
|
+
|
|
46
|
+
export type DirEventKind = "rename" | "change";
|
|
47
|
+
export type DirEventListener = (kind: DirEventKind, name: string) => void;
|
|
48
|
+
|
|
49
|
+
export interface DirWatch {
|
|
50
|
+
close(): void;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
interface Inotify {
|
|
54
|
+
fd: number;
|
|
55
|
+
addWatch: (fd: number, path: Uint8Array, mask: number) => number;
|
|
56
|
+
rmWatch: (fd: number, wd: number) => number;
|
|
57
|
+
read: (fd: number, buf: Pointer, size: number) => number | bigint;
|
|
58
|
+
buf: Uint8Array;
|
|
59
|
+
bufPtr: Pointer;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** `undefined` until first asked for; `null` where inotify cannot be reached. */
|
|
63
|
+
let inotify: Inotify | null | undefined;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Listeners per watch descriptor. Two trees watching one directory — a project nested inside
|
|
67
|
+
* another — are handed the same descriptor by the kernel, so the watch is removed only once the
|
|
68
|
+
* last of them lets go. The wrapper object keeps each registration distinct.
|
|
69
|
+
*/
|
|
70
|
+
const listeners = new Map<number, Set<{ listener: DirEventListener }>>();
|
|
71
|
+
const overflowListeners = new Set<() => void>();
|
|
72
|
+
let timer: ReturnType<typeof setInterval> | null = null;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Watches removed between two reads of the queue.
|
|
76
|
+
*
|
|
77
|
+
* Every `inotify_rm_watch` queues an IN_IGNORED, and a tree takes all of its watches down in one
|
|
78
|
+
* synchronous loop, so closing more directories than `max_queued_events` (16,384 by default)
|
|
79
|
+
* overflowed the queue by itself. The rebuild that answers an overflow takes the tree's watches
|
|
80
|
+
* down first, so a watched tree that size overflowed it again on every rebuild, for as long as
|
|
81
|
+
* it stayed open: 1.6 overflows a second and a third of a core on 20,000 directories. Reading as
|
|
82
|
+
* the watches go keeps the queue short. What is read waits in `pending` for the next drain, so
|
|
83
|
+
* no listener runs inside a `close()`.
|
|
84
|
+
*/
|
|
85
|
+
const READ_EVERY_REMOVALS = 1024;
|
|
86
|
+
let removedSinceRead = 0;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Read and not yet delivered. `kind: null` marks the kernel dropping a watch, kept in sequence so
|
|
90
|
+
* what came before it is still delivered.
|
|
91
|
+
*/
|
|
92
|
+
const pending: { wd: number; kind: DirEventKind | null; name: string }[] = [];
|
|
93
|
+
const pendingKeys = new Set<string>();
|
|
94
|
+
let pendingOverflow = false;
|
|
95
|
+
const encoder = new TextEncoder();
|
|
96
|
+
const decoder = new TextDecoder();
|
|
97
|
+
|
|
98
|
+
function open(): Inotify | null {
|
|
99
|
+
if (inotify !== undefined) return inotify;
|
|
100
|
+
inotify = null;
|
|
101
|
+
if (process.platform !== "linux") return null;
|
|
102
|
+
try {
|
|
103
|
+
const { symbols } = dlopen("libc.so.6", {
|
|
104
|
+
inotify_init1: { args: [FFIType.i32], returns: FFIType.i32 },
|
|
105
|
+
inotify_add_watch: { args: [FFIType.i32, FFIType.ptr, FFIType.u32], returns: FFIType.i32 },
|
|
106
|
+
inotify_rm_watch: { args: [FFIType.i32, FFIType.i32], returns: FFIType.i32 },
|
|
107
|
+
read: { args: [FFIType.i32, FFIType.ptr, FFIType.u64], returns: FFIType.i64 },
|
|
108
|
+
});
|
|
109
|
+
const fd = symbols.inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
|
|
110
|
+
if (fd < 0) return null; // out of inotify instances: the caller falls back to fs.watch
|
|
111
|
+
const buf = new Uint8Array(READ_BUFFER_BYTES);
|
|
112
|
+
inotify = {
|
|
113
|
+
fd,
|
|
114
|
+
addWatch: (f, path, mask) => symbols.inotify_add_watch(f, ptr(path), mask),
|
|
115
|
+
rmWatch: symbols.inotify_rm_watch,
|
|
116
|
+
read: symbols.read,
|
|
117
|
+
buf,
|
|
118
|
+
bufPtr: ptr(buf),
|
|
119
|
+
};
|
|
120
|
+
} catch {
|
|
121
|
+
// No glibc (a musl build): the caller falls back to fs.watch.
|
|
122
|
+
}
|
|
123
|
+
return inotify;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Whether `watchDirectory` can be used on this host. */
|
|
127
|
+
export function inotifyAvailable(): boolean {
|
|
128
|
+
return open() !== null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Watch one directory's entries — not its subdirectories' — and report each change by entry
|
|
133
|
+
* name. Null when the directory cannot be watched (gone, not a directory, a symlink, or the
|
|
134
|
+
* machine's watch limit is spent), which is where `fs.watch` would have thrown.
|
|
135
|
+
*/
|
|
136
|
+
export function watchDirectory(dir: string, listener: DirEventListener): DirWatch | null {
|
|
137
|
+
const lib = open();
|
|
138
|
+
if (!lib) return null;
|
|
139
|
+
const wd = lib.addWatch(lib.fd, encoder.encode(`${dir}\0`), WATCH_MASK);
|
|
140
|
+
if (wd < 0) return null;
|
|
141
|
+
const registrations = listeners.get(wd) ?? new Set();
|
|
142
|
+
listeners.set(wd, registrations);
|
|
143
|
+
const registration = { listener };
|
|
144
|
+
registrations.add(registration);
|
|
145
|
+
timer ??= setInterval(drain, POLL_MS);
|
|
146
|
+
timer.unref?.();
|
|
147
|
+
|
|
148
|
+
let closed = false;
|
|
149
|
+
return {
|
|
150
|
+
close() {
|
|
151
|
+
if (closed) return;
|
|
152
|
+
closed = true;
|
|
153
|
+
// The set is gone when the kernel already dropped the watch — and its number may since
|
|
154
|
+
// have been handed to another directory, which must not lose its watch.
|
|
155
|
+
if (listeners.get(wd) === registrations && registrations.delete(registration) && registrations.size === 0) {
|
|
156
|
+
listeners.delete(wd);
|
|
157
|
+
lib.rmWatch(lib.fd, wd);
|
|
158
|
+
if (++removedSinceRead >= READ_EVERY_REMOVALS) readQueue(lib);
|
|
159
|
+
}
|
|
160
|
+
if (listeners.size === 0 && timer) {
|
|
161
|
+
clearInterval(timer);
|
|
162
|
+
timer = null;
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Called when the kernel's event queue overflowed (`max_queued_events`) and events were lost,
|
|
170
|
+
* so a watcher has to rebuild what it covers rather than trust it.
|
|
171
|
+
*/
|
|
172
|
+
export function onInotifyOverflow(listener: () => void): () => void {
|
|
173
|
+
overflowListeners.add(listener);
|
|
174
|
+
return () => { overflowListeners.delete(listener); };
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** Move everything queued into `pending`, one entry per distinct event, in the order they happened. */
|
|
178
|
+
function readQueue(lib: Inotify): void {
|
|
179
|
+
removedSinceRead = 0;
|
|
180
|
+
for (;;) {
|
|
181
|
+
// -1 with EAGAIN when nothing is queued; any other failure is retried on the next tick.
|
|
182
|
+
const n = Number(lib.read(lib.fd, lib.bufPtr, READ_BUFFER_BYTES));
|
|
183
|
+
if (n <= 0) break;
|
|
184
|
+
const view = new DataView(lib.buf.buffer, 0, n);
|
|
185
|
+
for (let offset = 0; offset + EVENT_HEADER_BYTES <= n;) {
|
|
186
|
+
const wd = view.getInt32(offset, true);
|
|
187
|
+
const mask = view.getUint32(offset + 4, true);
|
|
188
|
+
const nameBytes = view.getUint32(offset + 12, true);
|
|
189
|
+
const raw = lib.buf.subarray(offset + EVENT_HEADER_BYTES, offset + EVENT_HEADER_BYTES + nameBytes);
|
|
190
|
+
offset += EVENT_HEADER_BYTES + nameBytes;
|
|
191
|
+
if (mask & IN_Q_OVERFLOW) {
|
|
192
|
+
pendingOverflow = true;
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
if (mask & IN_IGNORED) {
|
|
196
|
+
pending.push({ wd, kind: null, name: "" });
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
const end = raw.indexOf(0);
|
|
200
|
+
const name = decoder.decode(end < 0 ? raw : raw.subarray(0, end));
|
|
201
|
+
// Events about the watched directory itself carry no name; its parent reports those.
|
|
202
|
+
if (!name) continue;
|
|
203
|
+
const kind: DirEventKind = mask & RENAME_EVENTS ? "rename" : "change";
|
|
204
|
+
// A file written in a thousand chunks is a thousand IN_MODIFY; one report does.
|
|
205
|
+
const key = `${wd}\0${kind}\0${name}`;
|
|
206
|
+
if (pendingKeys.has(key)) continue;
|
|
207
|
+
pendingKeys.add(key);
|
|
208
|
+
pending.push({ wd, kind, name });
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** Read everything queued, then report each distinct event once, in the order they happened. */
|
|
214
|
+
function drain(): void {
|
|
215
|
+
const lib = inotify;
|
|
216
|
+
if (!lib) return;
|
|
217
|
+
readQueue(lib);
|
|
218
|
+
const events = pending.splice(0);
|
|
219
|
+
pendingKeys.clear();
|
|
220
|
+
const overflowed = pendingOverflow;
|
|
221
|
+
pendingOverflow = false;
|
|
222
|
+
for (const { wd, kind, name } of events) {
|
|
223
|
+
if (kind === null) {
|
|
224
|
+
listeners.delete(wd);
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
const registrations = listeners.get(wd);
|
|
228
|
+
if (!registrations) continue;
|
|
229
|
+
for (const { listener } of [...registrations]) {
|
|
230
|
+
try { listener(kind, name); }
|
|
231
|
+
catch (e) { console.error(`[file-watcher] listener failed: ${(e as Error).message}`); }
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (overflowed) {
|
|
235
|
+
console.warn("[file-watcher] inotify queue overflowed, rebuilding coverage");
|
|
236
|
+
for (const listener of [...overflowListeners]) listener();
|
|
237
|
+
}
|
|
238
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { lstatSync, readdirSync, watch
|
|
1
|
+
import { lstatSync, readdirSync, watch } from "node:fs";
|
|
2
2
|
import { join, relative, sep } from "node:path";
|
|
3
3
|
import { hasIgnoredDirSegment, isIgnoredDirName, isIgnoredPath } from "./ignore-rules.ts";
|
|
4
|
+
import { inotifyAvailable, onInotifyOverflow, watchDirectory } from "./linux-inotify.ts";
|
|
4
5
|
import { RecreatedDirPoller } from "./recreated-dir-poller.ts";
|
|
5
6
|
|
|
6
7
|
/**
|
|
@@ -36,6 +37,11 @@ import { RecreatedDirPoller } from "./recreated-dir-poller.ts";
|
|
|
36
37
|
* whole store watched, one open descriptor per file, which exhausted the process at
|
|
37
38
|
* ~724k descriptors.
|
|
38
39
|
*
|
|
40
|
+
* Linux no longer goes through `fs.watch` at all where glibc is present: each directory is
|
|
41
|
+
* one watch on a single inotify descriptor the whole process shares (`linux-inotify.ts`),
|
|
42
|
+
* because the per-file descriptors above still came to 76k on an ordinary set of projects.
|
|
43
|
+
* `fs.watch` remains the fallback there, and the recreated-directory poller with it.
|
|
44
|
+
*
|
|
39
45
|
* Either way the walk below is what prunes ~92% of the directories in a typical repo,
|
|
40
46
|
* and an ignored branch is never handed to the runtime.
|
|
41
47
|
*/
|
|
@@ -85,6 +91,11 @@ export interface WatchTreeOptions {
|
|
|
85
91
|
maxDirs: number;
|
|
86
92
|
/** Called with a root-relative POSIX path for every change worth reporting. */
|
|
87
93
|
onChange: (relPath: string) => void;
|
|
94
|
+
/**
|
|
95
|
+
* Watch through raw inotify rather than `fs.watch`. Defaults to whether this host can, which
|
|
96
|
+
* only a Linux host with glibc can; tests pass false to keep the fallback covered.
|
|
97
|
+
*/
|
|
98
|
+
inotify?: boolean;
|
|
88
99
|
}
|
|
89
100
|
|
|
90
101
|
interface ScanNode {
|
|
@@ -97,7 +108,7 @@ interface ScanNode {
|
|
|
97
108
|
}
|
|
98
109
|
|
|
99
110
|
interface AttachedWatcher {
|
|
100
|
-
watcher:
|
|
111
|
+
watcher: { close(): void };
|
|
101
112
|
/** Directories this watcher accounts for: the subtree when recursive, else itself. */
|
|
102
113
|
covers: number;
|
|
103
114
|
recursive: boolean;
|
|
@@ -106,7 +117,7 @@ interface AttachedWatcher {
|
|
|
106
117
|
export interface WatchTreeStats {
|
|
107
118
|
/** Directories covered ≈ inotify watches held on Linux. */
|
|
108
119
|
dirs: number;
|
|
109
|
-
/** `fs.watch`
|
|
120
|
+
/** Watch handles held: `fs.watch` watchers, or inotify watches on Linux. */
|
|
110
121
|
watchers: number;
|
|
111
122
|
/** Coverage was cut short by a budget, so part of the tree is unwatched. */
|
|
112
123
|
truncated: boolean;
|
|
@@ -127,6 +138,8 @@ export class WatchTree {
|
|
|
127
138
|
*/
|
|
128
139
|
private readonly everAttached = new Set<string>();
|
|
129
140
|
private readonly poller: RecreatedDirPoller | null;
|
|
141
|
+
private readonly inotify: boolean;
|
|
142
|
+
private readonly stopOverflowListener: () => void;
|
|
130
143
|
private covered = 0;
|
|
131
144
|
private truncated = false;
|
|
132
145
|
private closed = false;
|
|
@@ -142,14 +155,20 @@ export class WatchTree {
|
|
|
142
155
|
private coverChain: Promise<void> = Promise.resolve();
|
|
143
156
|
|
|
144
157
|
constructor(private readonly options: WatchTreeOptions) {
|
|
145
|
-
|
|
146
|
-
//
|
|
158
|
+
this.inotify = !NATIVE_RECURSIVE && (options.inotify ?? inotifyAvailable());
|
|
159
|
+
// Only Bun's `fs.watch` on Linux has the stale-watch defect; elsewhere re-watching works
|
|
160
|
+
// and paying for polling would be pure waste — raw inotify included, since a recreated
|
|
161
|
+
// directory is a new inode it simply watches.
|
|
147
162
|
// Tied to NATIVE_RECURSIVE rather than re-testing the platform: the poller stands in
|
|
148
163
|
// for a defect of the same emulation, and `attach()` relies on exactly one of the two
|
|
149
164
|
// being in play. Deriving it here keeps that from drifting apart.
|
|
150
|
-
this.poller = !NATIVE_RECURSIVE
|
|
165
|
+
this.poller = !NATIVE_RECURSIVE && !this.inotify
|
|
151
166
|
? new RecreatedDirPoller({ onChange: (abs) => this.reportAbs(abs) })
|
|
152
167
|
: null;
|
|
168
|
+
// Events were lost, so what is covered may have moved on: cover it again from the root.
|
|
169
|
+
this.stopOverflowListener = this.inotify
|
|
170
|
+
? onInotifyOverflow(() => this.scheduleRebuild(this.options.root))
|
|
171
|
+
: () => {};
|
|
153
172
|
}
|
|
154
173
|
|
|
155
174
|
async start(): Promise<void> {
|
|
@@ -159,6 +178,7 @@ export class WatchTree {
|
|
|
159
178
|
|
|
160
179
|
close(): void {
|
|
161
180
|
this.closed = true;
|
|
181
|
+
this.stopOverflowListener();
|
|
162
182
|
for (const timer of this.rebuildTimers.values()) clearTimeout(timer);
|
|
163
183
|
this.rebuildTimers.clear();
|
|
164
184
|
for (const { watcher } of this.attached.values()) {
|
|
@@ -347,6 +367,17 @@ export class WatchTree {
|
|
|
347
367
|
// `close()` has already emptied the map it would have been recorded in.
|
|
348
368
|
if (this.closed) return false;
|
|
349
369
|
if (this.attached.has(absDir)) return true;
|
|
370
|
+
if (this.inotify) {
|
|
371
|
+
const watcher = watchDirectory(absDir, (event, filename) => this.handleEvent(absDir, false, event, filename));
|
|
372
|
+
if (!watcher) {
|
|
373
|
+
this.truncated = true; // the same failures `fs.watch` throws for below
|
|
374
|
+
return false;
|
|
375
|
+
}
|
|
376
|
+
this.attached.set(absDir, { watcher, covers, recursive: false });
|
|
377
|
+
this.everAttached.add(absDir);
|
|
378
|
+
this.covered += covers;
|
|
379
|
+
return true;
|
|
380
|
+
}
|
|
350
381
|
try {
|
|
351
382
|
const watcher = watch(absDir, { recursive }, (event, filename) => {
|
|
352
383
|
this.handleEvent(absDir, recursive, event, typeof filename === "string" ? filename : null);
|