@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.
Files changed (641) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/CLAUDE.md +13 -2
  3. package/assets/skills/ppm/SKILL.md +1 -1
  4. package/assets/skills/ppm/references/cli-reference.md +4 -4
  5. package/assets/skills/ppm/references/http-api.md +37 -1
  6. package/bun.lock +34 -0
  7. package/dist/web/assets/accounts-settings-section-CGe5ikvs.js +1 -0
  8. package/dist/web/assets/accounts-settings-section-CGe5ikvs.js.br +0 -0
  9. package/dist/web/assets/accounts-settings-section-CGe5ikvs.js.gz +0 -0
  10. package/dist/web/assets/{agent-session-sheet-CBMsnl_p.js → agent-session-sheet-vBhfHYrC.js} +2 -2
  11. package/dist/web/assets/agent-session-sheet-vBhfHYrC.js.br +0 -0
  12. package/dist/web/assets/agent-session-sheet-vBhfHYrC.js.gz +0 -0
  13. package/dist/web/assets/{agent-session-window-content-D9C9FA7E.js → agent-session-window-content-BxIxDN5i.js} +1 -1
  14. package/dist/web/assets/agent-session-window-content-BxIxDN5i.js.br +0 -0
  15. package/dist/web/assets/agent-session-window-content-BxIxDN5i.js.gz +0 -0
  16. package/dist/web/assets/{ai-resource-editor-DhUVgDEG.js → ai-resource-editor-Y8clBPRv.js} +1 -1
  17. package/dist/web/assets/ai-resource-editor-Y8clBPRv.js.br +0 -0
  18. package/dist/web/assets/ai-resource-editor-Y8clBPRv.js.gz +0 -0
  19. package/dist/web/assets/{ai-settings-section-DOckWvp4.js → ai-settings-section-BB8vscte.js} +1 -1
  20. package/dist/web/assets/{ai-settings-section-CETpSpYd.js → ai-settings-section-Ck8uN_Bw.js} +1 -1
  21. package/dist/web/assets/ai-settings-section-Ck8uN_Bw.js.br +0 -0
  22. package/dist/web/assets/ai-settings-section-Ck8uN_Bw.js.gz +0 -0
  23. package/dist/web/assets/android-tab-BIOUVVoW.js +2 -0
  24. package/dist/web/assets/android-tab-BIOUVVoW.js.br +0 -0
  25. package/dist/web/assets/android-tab-BIOUVVoW.js.gz +0 -0
  26. package/dist/web/assets/{api-settings-JIgKfcCx.js → api-settings-D1axn9vm.js} +1 -1
  27. package/dist/web/assets/{appearance-settings-section-BJUiLUb7.js → appearance-settings-section-Bv5_n9lH.js} +1 -1
  28. package/dist/web/assets/appearance-settings-section-Bv5_n9lH.js.br +0 -0
  29. package/dist/web/assets/appearance-settings-section-Bv5_n9lH.js.gz +0 -0
  30. package/dist/web/assets/{apps-panel-1VElRgV3.js → apps-panel-DXHF0QEv.js} +1 -1
  31. package/dist/web/assets/apps-panel-DXHF0QEv.js.br +0 -0
  32. package/dist/web/assets/apps-panel-DXHF0QEv.js.gz +0 -0
  33. package/dist/web/assets/{audio-preview-DYkyHu5X.js → audio-preview-DpjOKEm4.js} +1 -1
  34. package/dist/web/assets/audio-preview-DpjOKEm4.js.br +0 -0
  35. package/dist/web/assets/audio-preview-DpjOKEm4.js.gz +0 -0
  36. package/dist/web/assets/branch-review-tab-4dgMNadQ.js +1 -0
  37. package/dist/web/assets/branch-review-tab-4dgMNadQ.js.br +0 -0
  38. package/dist/web/assets/branch-review-tab-4dgMNadQ.js.gz +0 -0
  39. package/dist/web/assets/chat-tab-Bbe_77oJ.js +15 -0
  40. package/dist/web/assets/chat-tab-Bbe_77oJ.js.br +0 -0
  41. package/dist/web/assets/chat-tab-Bbe_77oJ.js.gz +0 -0
  42. package/dist/web/assets/chat-tab-D3i5O3dO.js +1 -0
  43. package/dist/web/assets/chat-tab-D3i5O3dO.js.br +0 -0
  44. package/dist/web/assets/chat-tab-D3i5O3dO.js.gz +0 -0
  45. package/dist/web/assets/code-editor-DXsIWoQ1.js +7 -0
  46. package/dist/web/assets/code-editor-DXsIWoQ1.js.br +0 -0
  47. package/dist/web/assets/code-editor-DXsIWoQ1.js.gz +0 -0
  48. package/dist/web/assets/{codex-reset-credit-button-Fr45FUxi.js → codex-reset-credit-button-DRhievFQ.js} +1 -1
  49. package/dist/web/assets/codex-reset-credit-button-DRhievFQ.js.br +0 -0
  50. package/dist/web/assets/codex-reset-credit-button-DRhievFQ.js.gz +0 -0
  51. package/dist/web/assets/{conflict-editor-B-wdjSXI.js → conflict-editor-D_RImo_V.js} +3 -3
  52. package/dist/web/assets/conflict-editor-D_RImo_V.js.br +0 -0
  53. package/dist/web/assets/conflict-editor-D_RImo_V.js.gz +0 -0
  54. package/dist/web/assets/{csv-preview-B6RodObB.js → csv-preview-BuFdfLo3.js} +1 -1
  55. package/dist/web/assets/csv-preview-BuFdfLo3.js.br +0 -0
  56. package/dist/web/assets/csv-preview-BuFdfLo3.js.gz +0 -0
  57. package/dist/web/assets/{database-viewer-CdPBUQPY.js → database-viewer-ChcbYn9E.js} +1 -1
  58. package/dist/web/assets/database-viewer-ChcbYn9E.js.br +0 -0
  59. package/dist/web/assets/database-viewer-ChcbYn9E.js.gz +0 -0
  60. package/dist/web/assets/{design-settings-section-BpCpaBwL.js → design-settings-section-B_xOf51h.js} +1 -1
  61. package/dist/web/assets/design-settings-section-B_xOf51h.js.br +0 -0
  62. package/dist/web/assets/design-settings-section-B_xOf51h.js.gz +0 -0
  63. package/dist/web/assets/{design-skill-suggestion-DkT2E2IV.js → design-skill-suggestion-BbEDg15Z.js} +1 -1
  64. package/dist/web/assets/design-skill-suggestion-BbEDg15Z.js.br +0 -0
  65. package/dist/web/assets/design-skill-suggestion-BbEDg15Z.js.gz +0 -0
  66. package/dist/web/assets/{design-tab-BZb9eemI.js → design-tab-BQSYJ4cS.js} +6 -6
  67. package/dist/web/assets/design-tab-BQSYJ4cS.js.br +0 -0
  68. package/dist/web/assets/design-tab-BQSYJ4cS.js.gz +0 -0
  69. package/dist/web/assets/{designs-sidebar-panel-CF4tp6j-.js → designs-sidebar-panel-AiPaYfSU.js} +1 -1
  70. package/dist/web/assets/designs-sidebar-panel-AiPaYfSU.js.br +0 -0
  71. package/dist/web/assets/designs-sidebar-panel-AiPaYfSU.js.gz +0 -0
  72. package/dist/web/assets/{diff-viewer-BqEqwSw6.js → diff-viewer-BZ6glGK-.js} +1 -1
  73. package/dist/web/assets/diff-viewer-BZ6glGK-.js.br +0 -0
  74. package/dist/web/assets/diff-viewer-BZ6glGK-.js.gz +0 -0
  75. package/dist/web/assets/{edit-diff-preview-DD4HayXY.js → edit-diff-preview-D-FNLOU2.js} +1 -1
  76. package/dist/web/assets/edit-diff-preview-D-FNLOU2.js.br +0 -0
  77. package/dist/web/assets/edit-diff-preview-D-FNLOU2.js.gz +0 -0
  78. package/dist/web/assets/{editor-language-service-CkA5sEdV.js → editor-language-service-4sbsdqBV.js} +1 -1
  79. package/dist/web/assets/editor-language-service-4sbsdqBV.js.br +0 -0
  80. package/dist/web/assets/editor-language-service-4sbsdqBV.js.gz +0 -0
  81. package/dist/web/assets/explorer-body-DbgrG1fe.js +2 -0
  82. package/dist/web/assets/explorer-body-DbgrG1fe.js.br +0 -0
  83. package/dist/web/assets/explorer-body-DbgrG1fe.js.gz +0 -0
  84. package/dist/web/assets/{explorer-window-content-CRJ2eMZ4.js → explorer-window-content-BCjWvHpp.js} +1 -1
  85. package/dist/web/assets/explorer-window-content-BCjWvHpp.js.br +0 -0
  86. package/dist/web/assets/explorer-window-content-BCjWvHpp.js.gz +0 -0
  87. package/dist/web/assets/{extension-manager-section-DwQTdCII.js → extension-manager-section-DiMF4RnT.js} +1 -1
  88. package/dist/web/assets/extension-manager-section-DiMF4RnT.js.br +0 -0
  89. package/dist/web/assets/extension-manager-section-DiMF4RnT.js.gz +0 -0
  90. package/dist/web/assets/{extension-webview-CMq-VTLu.js → extension-webview-Coti6Vfw.js} +1 -1
  91. package/dist/web/assets/extension-webview-Coti6Vfw.js.br +0 -0
  92. package/dist/web/assets/extension-webview-Coti6Vfw.js.gz +0 -0
  93. package/dist/web/assets/file-store-Cm7-pHux.js +1 -0
  94. package/dist/web/assets/file-store-Cm7-pHux.js.br +0 -0
  95. package/dist/web/assets/file-store-Cm7-pHux.js.gz +0 -0
  96. package/dist/web/assets/{files-settings-section-B2lcyh-6.js → files-settings-section-ZTVwOwx0.js} +1 -1
  97. package/dist/web/assets/files-settings-section-ZTVwOwx0.js.br +0 -0
  98. package/dist/web/assets/files-settings-section-ZTVwOwx0.js.gz +0 -0
  99. package/dist/web/assets/{general-settings-section-Doepm2sH.js → general-settings-section-B7Hjx_fb.js} +1 -1
  100. package/dist/web/assets/general-settings-section-B7Hjx_fb.js.br +0 -0
  101. package/dist/web/assets/general-settings-section-B7Hjx_fb.js.gz +0 -0
  102. package/dist/web/assets/{git-log-panel-CJakNMDc.js → git-log-panel-CbcJ_YEH.js} +1 -1
  103. package/dist/web/assets/git-log-panel-CbcJ_YEH.js.br +0 -0
  104. package/dist/web/assets/git-log-panel-CbcJ_YEH.js.gz +0 -0
  105. package/dist/web/assets/{glide-data-grid-C_SeOhSq.js → glide-data-grid-BfByGSf1.js} +3 -3
  106. package/dist/web/assets/glide-data-grid-BfByGSf1.js.br +0 -0
  107. package/dist/web/assets/glide-data-grid-BfByGSf1.js.gz +0 -0
  108. package/dist/web/assets/group-chat-tab-DCo9OP3D.js +1 -0
  109. package/dist/web/assets/group-chat-tab-DCo9OP3D.js.br +0 -0
  110. package/dist/web/assets/group-chat-tab-DCo9OP3D.js.gz +0 -0
  111. package/dist/web/assets/{image-preview-C1Frlx22.js → image-preview-Cjlps7Pt.js} +1 -1
  112. package/dist/web/assets/index-CyDLBddf.js +52 -0
  113. package/dist/web/assets/index-CyDLBddf.js.br +0 -0
  114. package/dist/web/assets/index-CyDLBddf.js.gz +0 -0
  115. package/dist/web/assets/index-Dh5apC3I.css +1 -0
  116. package/dist/web/assets/index-Dh5apC3I.css.br +0 -0
  117. package/dist/web/assets/index-Dh5apC3I.css.gz +0 -0
  118. package/dist/web/assets/{jira-watcher-section-BmkIGQFV.js → jira-watcher-section-Dz_-sUC2.js} +1 -1
  119. package/dist/web/assets/keybindings-store-BTq5Z7XP.js +1 -0
  120. package/dist/web/assets/{keyboard-shortcuts-section-BgZe3QDr.js → keyboard-shortcuts-section-Ba5NYwlG.js} +1 -1
  121. package/dist/web/assets/keyboard-shortcuts-section-Ba5NYwlG.js.br +0 -0
  122. package/dist/web/assets/keyboard-shortcuts-section-Ba5NYwlG.js.gz +0 -0
  123. package/dist/web/assets/{language-servers-section-CCMzgOaE.js → language-servers-section-DJJ7SFXo.js} +2 -2
  124. package/dist/web/assets/language-servers-section-DJJ7SFXo.js.br +0 -0
  125. package/dist/web/assets/language-servers-section-DJJ7SFXo.js.gz +0 -0
  126. package/dist/web/assets/{lsp-client-B10suuaZ.js → lsp-client-Dr3vQ1bb.js} +1 -1
  127. package/dist/web/assets/lsp-client-Dr3vQ1bb.js.br +0 -0
  128. package/dist/web/assets/lsp-client-Dr3vQ1bb.js.gz +0 -0
  129. package/dist/web/assets/{markdown-renderer-AHHFS3jc.js → markdown-renderer-CmnqFfTU.js} +3 -3
  130. package/dist/web/assets/markdown-renderer-CmnqFfTU.js.br +0 -0
  131. package/dist/web/assets/markdown-renderer-CmnqFfTU.js.gz +0 -0
  132. package/dist/web/assets/markdown-renderer-LJdVMbGl.js +1 -0
  133. package/dist/web/assets/{mobile-explorer-sheet-CqCsLWSr.js → mobile-explorer-sheet-BiIhZtJ2.js} +1 -1
  134. package/dist/web/assets/mobile-explorer-sheet-BiIhZtJ2.js.br +0 -0
  135. package/dist/web/assets/mobile-explorer-sheet-BiIhZtJ2.js.gz +0 -0
  136. package/dist/web/assets/new-design-dialog-DI_hxvFV.js +1 -0
  137. package/dist/web/assets/new-design-dialog-DI_hxvFV.js.br +0 -0
  138. package/dist/web/assets/new-design-dialog-DI_hxvFV.js.gz +0 -0
  139. package/dist/web/assets/notification-store-9lBSplOV.js +1 -0
  140. package/dist/web/assets/{notifications-settings-section-CXgzH_bK.js → notifications-settings-section-0s-2OKRv.js} +1 -1
  141. package/dist/web/assets/notifications-settings-section-0s-2OKRv.js.br +0 -0
  142. package/dist/web/assets/notifications-settings-section-0s-2OKRv.js.gz +0 -0
  143. package/dist/web/assets/{overview-panel-BYB8mgRE.js → overview-panel-3htxfZS2.js} +1 -1
  144. package/dist/web/assets/overview-panel-3htxfZS2.js.br +0 -0
  145. package/dist/web/assets/overview-panel-3htxfZS2.js.gz +0 -0
  146. package/dist/web/assets/panel-store-B6PoCwBA.js +1 -0
  147. package/dist/web/assets/panel-store-B6PoCwBA.js.br +0 -0
  148. package/dist/web/assets/panel-store-B6PoCwBA.js.gz +0 -0
  149. package/dist/web/assets/{performance-panel-Dpuq3g0W.js → performance-panel-V8gpnnf7.js} +2 -2
  150. package/dist/web/assets/performance-panel-V8gpnnf7.js.br +0 -0
  151. package/dist/web/assets/performance-panel-V8gpnnf7.js.gz +0 -0
  152. package/dist/web/assets/{postgres-viewer-DW0Gkmow.js → postgres-viewer-sWS-ogTC.js} +1 -1
  153. package/dist/web/assets/postgres-viewer-sWS-ogTC.js.br +0 -0
  154. package/dist/web/assets/postgres-viewer-sWS-ogTC.js.gz +0 -0
  155. package/dist/web/assets/{ppmbot-settings-section-DFtlGoW6.js → ppmbot-settings-section-CT8vvlzk.js} +1 -1
  156. package/dist/web/assets/ppmbot-settings-section-CT8vvlzk.js.br +0 -0
  157. package/dist/web/assets/ppmbot-settings-section-CT8vvlzk.js.gz +0 -0
  158. package/dist/web/assets/{pptx-export-CNyG32Xr.js → pptx-export-Ugjv0hso.js} +4 -4
  159. package/dist/web/assets/pptx-export-Ugjv0hso.js.br +0 -0
  160. package/dist/web/assets/pptx-export-Ugjv0hso.js.gz +0 -0
  161. package/dist/web/assets/{problems-panel-V5zqpuXq.js → problems-panel-Gcy79pSS.js} +1 -1
  162. package/dist/web/assets/problems-panel-Gcy79pSS.js.br +0 -0
  163. package/dist/web/assets/problems-panel-Gcy79pSS.js.gz +0 -0
  164. package/dist/web/assets/{process-row-format-CiJJSf_V.js → process-row-format-u6y7lOee.js} +1 -1
  165. package/dist/web/assets/process-table-BUTIp9Oy.js +1 -0
  166. package/dist/web/assets/process-table-BUTIp9Oy.js.br +0 -0
  167. package/dist/web/assets/process-table-BUTIp9Oy.js.gz +0 -0
  168. package/dist/web/assets/{proxy-settings-section-B7AVlTfY.js → proxy-settings-section-Buv_8y54.js} +1 -1
  169. package/dist/web/assets/proxy-settings-section-Buv_8y54.js.br +0 -0
  170. package/dist/web/assets/proxy-settings-section-Buv_8y54.js.gz +0 -0
  171. package/dist/web/assets/{query-audit-section-gHmRfWKr.js → query-audit-section-N2DQtgJx.js} +1 -1
  172. package/dist/web/assets/query-audit-section-N2DQtgJx.js.br +0 -0
  173. package/dist/web/assets/query-audit-section-N2DQtgJx.js.gz +0 -0
  174. package/dist/web/assets/remote-desktop-clipboard-notice-CRvJqmaz.js +1 -0
  175. package/dist/web/assets/remote-desktop-clipboard-notice-CRvJqmaz.js.br +0 -0
  176. package/dist/web/assets/remote-desktop-clipboard-notice-CRvJqmaz.js.gz +0 -0
  177. package/dist/web/assets/remote-desktop-mobile-sheet-BoYINLbJ.js +2 -0
  178. package/dist/web/assets/remote-desktop-mobile-sheet-BoYINLbJ.js.br +0 -0
  179. package/dist/web/assets/remote-desktop-mobile-sheet-BoYINLbJ.js.gz +0 -0
  180. package/dist/web/assets/remote-desktop-mobile-view-DPdP0yYu.js +1 -0
  181. package/dist/web/assets/remote-desktop-mobile-view-DPdP0yYu.js.br +0 -0
  182. package/dist/web/assets/remote-desktop-mobile-view-DPdP0yYu.js.gz +0 -0
  183. package/dist/web/assets/remote-desktop-readiness-gate-BeQ_Jrp7.js +1 -0
  184. package/dist/web/assets/remote-desktop-readiness-gate-BeQ_Jrp7.js.br +0 -0
  185. package/dist/web/assets/remote-desktop-readiness-gate-BeQ_Jrp7.js.gz +0 -0
  186. package/dist/web/assets/remote-desktop-settings-section-Bs4sHS3l.js +1 -0
  187. package/dist/web/assets/remote-desktop-settings-section-Bs4sHS3l.js.br +0 -0
  188. package/dist/web/assets/remote-desktop-settings-section-Bs4sHS3l.js.gz +0 -0
  189. package/dist/web/assets/remote-desktop-window-content-DDr__JxQ.js +1 -0
  190. package/dist/web/assets/remote-desktop-window-content-DDr__JxQ.js.br +0 -0
  191. package/dist/web/assets/remote-desktop-window-content-DDr__JxQ.js.gz +0 -0
  192. package/dist/web/assets/{run-in-terminal-DKBcEGVu.js → run-in-terminal-DCBNtYHQ.js} +1 -1
  193. package/dist/web/assets/{schedules-settings-section-sYDAsBMO.js → schedules-settings-section-_cHX2qOa.js} +1 -1
  194. package/dist/web/assets/schedules-settings-section-_cHX2qOa.js.br +0 -0
  195. package/dist/web/assets/schedules-settings-section-_cHX2qOa.js.gz +0 -0
  196. package/dist/web/assets/{send-to-chat-CCdOrKc7.js → send-to-chat-dNqNV7Y4.js} +1 -1
  197. package/dist/web/assets/send-to-chat-dNqNV7Y4.js.br +0 -0
  198. package/dist/web/assets/send-to-chat-dNqNV7Y4.js.gz +0 -0
  199. package/dist/web/assets/{services-panel-CKa2aP1d.js → services-panel-Dm54cnW3.js} +1 -1
  200. package/dist/web/assets/services-panel-Dm54cnW3.js.br +0 -0
  201. package/dist/web/assets/services-panel-Dm54cnW3.js.gz +0 -0
  202. package/dist/web/assets/settings-body-DRRQNaDA.js +1 -0
  203. package/dist/web/assets/settings-body-DRRQNaDA.js.br +0 -0
  204. package/dist/web/assets/settings-body-DRRQNaDA.js.gz +0 -0
  205. package/dist/web/assets/settings-section-content-DRaHfvm8.js +1 -0
  206. package/dist/web/assets/settings-section-content-U3mIx6hF.js +2 -0
  207. package/dist/web/assets/settings-section-content-U3mIx6hF.js.br +0 -0
  208. package/dist/web/assets/settings-section-content-U3mIx6hF.js.gz +0 -0
  209. package/dist/web/assets/{settings-store-DfuiLq_9.js → settings-store-Cyuo8ceh.js} +2 -2
  210. package/dist/web/assets/settings-store-Cyuo8ceh.js.br +0 -0
  211. package/dist/web/assets/settings-store-Cyuo8ceh.js.gz +0 -0
  212. package/dist/web/assets/settings-tab-CB_BhHTK.js +1 -0
  213. package/dist/web/assets/settings-window-content-CgZIMNX8.js +1 -0
  214. package/dist/web/assets/{slash-command-picker-CLevA_Gf.js → slash-command-picker-CQlIqMW0.js} +1 -1
  215. package/dist/web/assets/slash-command-picker-CQlIqMW0.js.br +0 -0
  216. package/dist/web/assets/slash-command-picker-CQlIqMW0.js.gz +0 -0
  217. package/dist/web/assets/{sql-query-editor-Db8b0jEr.js → sql-query-editor--Z4bcqd8.js} +1 -1
  218. package/dist/web/assets/sql-query-editor--Z4bcqd8.js.br +0 -0
  219. package/dist/web/assets/sql-query-editor--Z4bcqd8.js.gz +0 -0
  220. package/dist/web/assets/{sqlite-viewer-VzYLMqkD.js → sqlite-viewer-CSmX3qaj.js} +1 -1
  221. package/dist/web/assets/sqlite-viewer-CSmX3qaj.js.br +0 -0
  222. package/dist/web/assets/sqlite-viewer-CSmX3qaj.js.gz +0 -0
  223. package/dist/web/assets/{system-monitor-body-BR60W_mG.js → system-monitor-body-LannpU6H.js} +2 -2
  224. package/dist/web/assets/system-monitor-body-LannpU6H.js.br +0 -0
  225. package/dist/web/assets/system-monitor-body-LannpU6H.js.gz +0 -0
  226. package/dist/web/assets/system-monitor-tab-BfVucwEF.js +1 -0
  227. package/dist/web/assets/{system-monitor-window-content-BLw1QBXo.js → system-monitor-window-content-kLNW9Ln3.js} +1 -1
  228. package/dist/web/assets/{tab-host-window-content-BL61SB5m.js → tab-host-window-content-II_FtOYM.js} +1 -1
  229. package/dist/web/assets/tab-host-window-content-II_FtOYM.js.br +0 -0
  230. package/dist/web/assets/tab-host-window-content-II_FtOYM.js.gz +0 -0
  231. package/dist/web/assets/{tab-store-CbPh8CAX.js → tab-store-DCpc0g0Q.js} +1 -1
  232. package/dist/web/assets/{terminal-tab-B4y6at4h.js → terminal-tab-CTe2polt.js} +1 -1
  233. package/dist/web/assets/terminal-tab-CTe2polt.js.br +0 -0
  234. package/dist/web/assets/terminal-tab-CTe2polt.js.gz +0 -0
  235. package/dist/web/assets/{tool-cards-K0r_yxI0.js → tool-cards-rxLXbBSm.js} +5 -5
  236. package/dist/web/assets/tool-cards-rxLXbBSm.js.br +0 -0
  237. package/dist/web/assets/tool-cards-rxLXbBSm.js.gz +0 -0
  238. package/dist/web/assets/use-h264-canvas-decoder-BfO9QrlH.js +1 -0
  239. package/dist/web/assets/use-h264-canvas-decoder-BfO9QrlH.js.br +0 -0
  240. package/dist/web/assets/use-h264-canvas-decoder-BfO9QrlH.js.gz +0 -0
  241. package/dist/web/assets/{use-inline-blame-BK23icsE.js → use-inline-blame-CxZ_QWzS.js} +3 -3
  242. package/dist/web/assets/use-inline-blame-CxZ_QWzS.js.br +0 -0
  243. package/dist/web/assets/use-inline-blame-CxZ_QWzS.js.gz +0 -0
  244. package/dist/web/assets/{use-lsp-CRdCPCKW.js → use-lsp-gCNE5A9K.js} +1 -1
  245. package/dist/web/assets/use-lsp-gCNE5A9K.js.br +0 -0
  246. package/dist/web/assets/use-lsp-gCNE5A9K.js.gz +0 -0
  247. package/dist/web/assets/{use-monaco-theme-CpXT2uh3.js → use-monaco-theme-DvNGcCvz.js} +1 -1
  248. package/dist/web/assets/{use-process-kill-BWYX03KF.js → use-process-kill-Cbiu1mbY.js} +1 -1
  249. package/dist/web/assets/use-process-kill-Cbiu1mbY.js.br +0 -0
  250. package/dist/web/assets/use-process-kill-Cbiu1mbY.js.gz +0 -0
  251. package/dist/web/assets/{use-websocket-CiWQKhHr.js → use-websocket-AC7EG1LK.js} +1 -1
  252. package/dist/web/assets/video-preview-C2Y61WUE.js +1 -0
  253. package/dist/web/assets/video-preview-C2Y61WUE.js.br +0 -0
  254. package/dist/web/assets/video-preview-C2Y61WUE.js.gz +0 -0
  255. package/dist/web/assets/{voice-settings-section-Ceihggqf.js → voice-settings-section-DCXZeLYI.js} +1 -1
  256. package/dist/web/assets/voice-settings-section-DCXZeLYI.js.br +0 -0
  257. package/dist/web/assets/voice-settings-section-DCXZeLYI.js.gz +0 -0
  258. package/dist/web/assets/{window-pip-registry-mDgwpAwP.js → window-pip-registry-9Y9LQQYS.js} +1 -1
  259. package/dist/web/index.html +50 -7
  260. package/dist/web/index.html.br +0 -0
  261. package/dist/web/index.html.gz +0 -0
  262. package/dist/web/sw.js +1 -1
  263. package/dist/web/sw.js.br +0 -0
  264. package/dist/web/sw.js.gz +0 -0
  265. package/docs/architecture/android-emulator-phase0-report-260921.md +310 -0
  266. package/docs/architecture/android-emulator-phase2-report-260921.md +163 -0
  267. package/docs/architecture/android-emulator-phase3-report-260921.md +203 -0
  268. package/docs/architecture/android-emulator-phase4-report-260921.md +191 -0
  269. package/docs/architecture/android-emulator-plan-260920.md +376 -0
  270. package/docs/architecture/plugins-and-tracing.md +48 -7
  271. package/package.json +4 -2
  272. package/scripts/gen-android-proto.ts +37 -0
  273. package/scripts/release.sh +3 -1
  274. package/src/cli/commands/chat-cmd.ts +2 -2
  275. package/src/providers/claude-agent-sdk-query-options.ts +28 -0
  276. package/src/providers/claude-agent-sdk.ts +153 -71
  277. package/src/providers/claude-warm-spare.ts +176 -0
  278. package/src/providers/provider.interface.ts +1 -0
  279. package/src/server/index.ts +53 -0
  280. package/src/server/middleware/gzip-json.ts +13 -1
  281. package/src/server/routes/android.ts +616 -0
  282. package/src/server/routes/chat.ts +31 -0
  283. package/src/server/routes/files.ts +52 -5
  284. package/src/server/routes/remote-desktop.ts +95 -2
  285. package/src/server/routes/trace.ts +66 -0
  286. package/src/server/ws/android.ts +101 -0
  287. package/src/server/ws/chat.ts +12 -17
  288. package/src/server/ws/global.ts +13 -1
  289. package/src/server/ws/remote-desktop.ts +6 -1
  290. package/src/services/android/android-apk.ts +387 -0
  291. package/src/services/android/android-capabilities.ts +154 -0
  292. package/src/services/android/android-flag.ts +12 -0
  293. package/src/services/android/android-grpc.ts +124 -0
  294. package/src/services/android/android-input.ts +129 -0
  295. package/src/services/android/android-logcat.ts +169 -0
  296. package/src/services/android/android-nonce.ts +50 -0
  297. package/src/services/android/android-operations.ts +124 -0
  298. package/src/services/android/android-screenshot.ts +53 -0
  299. package/src/services/android/android-session.ts +510 -0
  300. package/src/services/android/android-video.ts +495 -0
  301. package/src/services/android/avd-list.ts +131 -0
  302. package/src/services/android/avd-manager.ts +397 -0
  303. package/src/services/android/device-registry.ts +223 -0
  304. package/src/services/android/emulator-discovery.ts +160 -0
  305. package/src/services/android/emulator-launcher.ts +290 -0
  306. package/src/services/android/proto/emulator-controller-descriptor.ts +10 -0
  307. package/src/services/android/proto/emulator_controller.proto +1791 -0
  308. package/src/services/android/sdk-discovery.ts +178 -0
  309. package/src/services/android/system-images.ts +124 -0
  310. package/src/services/chat.service.ts +81 -4
  311. package/src/services/config.service.ts +1 -1
  312. package/src/services/file-filter.service.ts +2 -61
  313. package/src/services/file-glob.ts +67 -0
  314. package/src/services/file-index/file-index-runner.ts +169 -0
  315. package/src/services/file-index/file-index-worker.ts +73 -0
  316. package/src/services/file-index/index-search.ts +152 -0
  317. package/src/services/file-index/index-walk.ts +109 -0
  318. package/src/services/file-list-index.service.ts +189 -70
  319. package/src/services/file-watcher/linux-inotify.ts +238 -0
  320. package/src/services/file-watcher/watch-tree.ts +37 -6
  321. package/src/services/file-watcher.service.ts +40 -8
  322. package/src/services/file.service.ts +17 -6
  323. package/src/services/group-chat/group-chat.service.ts +1 -1
  324. package/src/services/jira-debug-session.service.ts +1 -1
  325. package/src/services/ppmbot/ppmbot-delegation.ts +1 -1
  326. package/src/services/ppmbot/ppmbot-service.ts +1 -1
  327. package/src/services/project-content-search.service.ts +45 -11
  328. package/src/services/proxy-agent-turn.ts +7 -3
  329. package/src/services/remote-desktop/mediamtx-catalog.ts +89 -0
  330. package/src/services/remote-desktop/mediamtx-config.ts +91 -0
  331. package/src/services/remote-desktop/mediamtx-install.service.ts +191 -0
  332. package/src/services/remote-desktop/mediamtx-paths.ts +51 -0
  333. package/src/services/remote-desktop/mediamtx-process.ts +152 -0
  334. package/src/services/remote-desktop/remote-desktop-capture-wayland.ts +312 -0
  335. package/src/services/remote-desktop/remote-desktop-capture.ts +60 -4
  336. package/src/services/remote-desktop/remote-desktop-portal.ts +529 -0
  337. package/src/services/remote-desktop/remote-desktop-requirements.ts +56 -20
  338. package/src/services/remote-desktop/remote-desktop-session.ts +77 -3
  339. package/src/services/remote-desktop/remote-desktop-whep-registry.ts +36 -0
  340. package/src/services/scheduler-runner.ts +2 -3
  341. package/src/services/session-trace/browser-trace-ingest.ts +131 -0
  342. package/src/services/session-trace/session-trace-cleanup.ts +65 -0
  343. package/src/services/session-trace/session-trace-db.ts +89 -0
  344. package/src/services/session-trace/session-trace-store.ts +132 -0
  345. package/src/services/session-trace/trace-coalescer.ts +86 -0
  346. package/src/services/session-trace/trace-recorder.ts +304 -0
  347. package/src/services/session-trace/trace-writer.ts +83 -0
  348. package/src/shared/android-avd.ts +53 -0
  349. package/src/shared/android-protocol.ts +184 -0
  350. package/src/shared/file-index-limits.ts +17 -0
  351. package/src/shared/session-trace.ts +61 -0
  352. package/src/types/chat.ts +17 -0
  353. package/src/types/config.ts +27 -0
  354. package/src/types/project.ts +9 -0
  355. package/src/web/app.tsx +5 -0
  356. package/src/web/components/android/android-apk-upload.ts +75 -0
  357. package/src/web/components/android/android-avd-create-dialog.tsx +305 -0
  358. package/src/web/components/android/android-avd-destroy-dialog.tsx +151 -0
  359. package/src/web/components/android/android-clipboard-panel.tsx +94 -0
  360. package/src/web/components/android/android-controls.tsx +128 -0
  361. package/src/web/components/android/android-coords.ts +99 -0
  362. package/src/web/components/android/android-device-list.tsx +329 -0
  363. package/src/web/components/android/android-install-panel.tsx +280 -0
  364. package/src/web/components/android/android-log-filter.ts +75 -0
  365. package/src/web/components/android/android-logcat-panel.tsx +214 -0
  366. package/src/web/components/android/android-screenshot-download.ts +56 -0
  367. package/src/web/components/android/android-tab.tsx +66 -0
  368. package/src/web/components/android/android-tools-drawer.tsx +76 -0
  369. package/src/web/components/android/android-viewer.tsx +200 -0
  370. package/src/web/components/android/android-ws-url.ts +21 -0
  371. package/src/web/components/android/open-android.ts +21 -0
  372. package/src/web/components/android/use-android-available.ts +29 -0
  373. package/src/web/components/android/use-android-keyboard.ts +111 -0
  374. package/src/web/components/android/use-android-session.ts +277 -0
  375. package/src/web/components/android/use-android-touch.ts +123 -0
  376. package/src/web/components/chat/chat-tab.tsx +28 -2
  377. package/src/web/components/chat/message-input.tsx +3 -5
  378. package/src/web/components/editor/compare-picker.tsx +12 -2
  379. package/src/web/components/explorer/file-tree.tsx +6 -7
  380. package/src/web/components/explorer/use-tree-row-dnd.ts +0 -2
  381. package/src/web/components/floating-window/window-content-registry.ts +8 -8
  382. package/src/web/components/layout/command-palette.tsx +31 -9
  383. package/src/web/components/layout/mobile-drawer.tsx +8 -1
  384. package/src/web/components/layout/nav-section-rail.tsx +8 -1
  385. package/src/web/components/layout/reparenting-tab.tsx +2 -2
  386. package/src/web/components/layout/tab-content.tsx +5 -0
  387. package/src/web/components/layout/tab-pool.tsx +31 -20
  388. package/src/web/components/remote-desktop/use-h264-canvas-decoder.ts +23 -1
  389. package/src/web/components/remote-desktop/use-remote-desktop-connection.ts +64 -6
  390. package/src/web/components/remote-desktop/use-webrtc-canvas-video.ts +255 -0
  391. package/src/web/components/root-error-boundary.tsx +5 -0
  392. package/src/web/components/settings/remote-desktop-settings-section.tsx +176 -0
  393. package/src/web/components/settings/settings-categories.ts +3 -0
  394. package/src/web/components/settings/settings-section-content.tsx +25 -18
  395. package/src/web/hooks/use-chat-prewarm.ts +53 -0
  396. package/src/web/hooks/use-chat.ts +18 -4
  397. package/src/web/hooks/use-file-index-invalidation.ts +7 -5
  398. package/src/web/hooks/use-global-events.ts +12 -1
  399. package/src/web/hooks/use-remote-file-search.ts +39 -0
  400. package/src/web/hooks/use-ui-preload.ts +38 -0
  401. package/src/web/index.html +43 -0
  402. package/src/web/lib/chunk-recovery.ts +5 -1
  403. package/src/web/lib/device-id.ts +39 -0
  404. package/src/web/lib/feature-badges.ts +2 -0
  405. package/src/web/lib/lazy-with-preload.tsx +43 -0
  406. package/src/web/lib/preload-ui.ts +60 -0
  407. package/src/web/lib/score-file-search.ts +27 -14
  408. package/src/web/lib/tab-type-icons.ts +2 -1
  409. package/src/web/lib/trace-boot.ts +8 -0
  410. package/src/web/lib/trace-client.ts +516 -0
  411. package/src/web/main.tsx +12 -0
  412. package/src/web/stores/file-store.ts +124 -18
  413. package/src/web/stores/panel-utils.ts +3 -0
  414. package/src/web/stores/tab-store.ts +2 -1
  415. package/src/web/stores/window-panel-persistence.ts +3 -0
  416. package/vite.config.ts +3 -0
  417. package/dist/web/assets/accounts-settings-section-DEIm-W-D.js +0 -1
  418. package/dist/web/assets/accounts-settings-section-DEIm-W-D.js.br +0 -0
  419. package/dist/web/assets/accounts-settings-section-DEIm-W-D.js.gz +0 -0
  420. package/dist/web/assets/agent-session-sheet-CBMsnl_p.js.br +0 -0
  421. package/dist/web/assets/agent-session-sheet-CBMsnl_p.js.gz +0 -0
  422. package/dist/web/assets/agent-session-window-content-D9C9FA7E.js.br +0 -0
  423. package/dist/web/assets/agent-session-window-content-D9C9FA7E.js.gz +0 -0
  424. package/dist/web/assets/ai-resource-editor-DhUVgDEG.js.br +0 -0
  425. package/dist/web/assets/ai-resource-editor-DhUVgDEG.js.gz +0 -0
  426. package/dist/web/assets/ai-settings-section-CETpSpYd.js.br +0 -0
  427. package/dist/web/assets/ai-settings-section-CETpSpYd.js.gz +0 -0
  428. package/dist/web/assets/appearance-settings-section-BJUiLUb7.js.br +0 -0
  429. package/dist/web/assets/appearance-settings-section-BJUiLUb7.js.gz +0 -0
  430. package/dist/web/assets/apps-panel-1VElRgV3.js.br +0 -0
  431. package/dist/web/assets/apps-panel-1VElRgV3.js.gz +0 -0
  432. package/dist/web/assets/audio-preview-DYkyHu5X.js.br +0 -0
  433. package/dist/web/assets/audio-preview-DYkyHu5X.js.gz +0 -0
  434. package/dist/web/assets/branch-review-tab-DhTMSpSU.js +0 -1
  435. package/dist/web/assets/branch-review-tab-DhTMSpSU.js.br +0 -0
  436. package/dist/web/assets/branch-review-tab-DhTMSpSU.js.gz +0 -0
  437. package/dist/web/assets/chat-tab-Cg3BKugo.js +0 -15
  438. package/dist/web/assets/chat-tab-Cg3BKugo.js.br +0 -0
  439. package/dist/web/assets/chat-tab-Cg3BKugo.js.gz +0 -0
  440. package/dist/web/assets/chat-tab-tbUDK__Y.js +0 -1
  441. package/dist/web/assets/chat-tab-tbUDK__Y.js.br +0 -0
  442. package/dist/web/assets/chat-tab-tbUDK__Y.js.gz +0 -0
  443. package/dist/web/assets/code-editor-6ZiQehbr.js +0 -7
  444. package/dist/web/assets/code-editor-6ZiQehbr.js.br +0 -0
  445. package/dist/web/assets/code-editor-6ZiQehbr.js.gz +0 -0
  446. package/dist/web/assets/codex-reset-credit-button-Fr45FUxi.js.br +0 -0
  447. package/dist/web/assets/codex-reset-credit-button-Fr45FUxi.js.gz +0 -0
  448. package/dist/web/assets/conflict-editor-B-wdjSXI.js.br +0 -0
  449. package/dist/web/assets/conflict-editor-B-wdjSXI.js.gz +0 -0
  450. package/dist/web/assets/csv-preview-B6RodObB.js.br +0 -0
  451. package/dist/web/assets/csv-preview-B6RodObB.js.gz +0 -0
  452. package/dist/web/assets/database-viewer-CdPBUQPY.js.br +0 -0
  453. package/dist/web/assets/database-viewer-CdPBUQPY.js.gz +0 -0
  454. package/dist/web/assets/design-settings-section-BpCpaBwL.js.br +0 -0
  455. package/dist/web/assets/design-settings-section-BpCpaBwL.js.gz +0 -0
  456. package/dist/web/assets/design-skill-suggestion-DkT2E2IV.js.br +0 -0
  457. package/dist/web/assets/design-skill-suggestion-DkT2E2IV.js.gz +0 -0
  458. package/dist/web/assets/design-tab-BZb9eemI.js.br +0 -0
  459. package/dist/web/assets/design-tab-BZb9eemI.js.gz +0 -0
  460. package/dist/web/assets/designs-sidebar-panel-CF4tp6j-.js.br +0 -0
  461. package/dist/web/assets/designs-sidebar-panel-CF4tp6j-.js.gz +0 -0
  462. package/dist/web/assets/diff-viewer-BqEqwSw6.js.br +0 -0
  463. package/dist/web/assets/diff-viewer-BqEqwSw6.js.gz +0 -0
  464. package/dist/web/assets/edit-diff-preview-DD4HayXY.js.br +0 -0
  465. package/dist/web/assets/edit-diff-preview-DD4HayXY.js.gz +0 -0
  466. package/dist/web/assets/editor-language-service-CkA5sEdV.js.br +0 -0
  467. package/dist/web/assets/editor-language-service-CkA5sEdV.js.gz +0 -0
  468. package/dist/web/assets/explorer-body-Ds7oKuz7.js +0 -2
  469. package/dist/web/assets/explorer-body-Ds7oKuz7.js.br +0 -0
  470. package/dist/web/assets/explorer-body-Ds7oKuz7.js.gz +0 -0
  471. package/dist/web/assets/explorer-window-content-CRJ2eMZ4.js.br +0 -0
  472. package/dist/web/assets/explorer-window-content-CRJ2eMZ4.js.gz +0 -0
  473. package/dist/web/assets/extension-manager-section-DwQTdCII.js.br +0 -0
  474. package/dist/web/assets/extension-manager-section-DwQTdCII.js.gz +0 -0
  475. package/dist/web/assets/extension-webview-CMq-VTLu.js.br +0 -0
  476. package/dist/web/assets/extension-webview-CMq-VTLu.js.gz +0 -0
  477. package/dist/web/assets/file-store-DY1_0nzY.js +0 -1
  478. package/dist/web/assets/file-store-DY1_0nzY.js.br +0 -0
  479. package/dist/web/assets/file-store-DY1_0nzY.js.gz +0 -0
  480. package/dist/web/assets/files-settings-section-B2lcyh-6.js.br +0 -0
  481. package/dist/web/assets/files-settings-section-B2lcyh-6.js.gz +0 -0
  482. package/dist/web/assets/general-settings-section-Doepm2sH.js.br +0 -0
  483. package/dist/web/assets/general-settings-section-Doepm2sH.js.gz +0 -0
  484. package/dist/web/assets/git-log-panel-CJakNMDc.js.br +0 -0
  485. package/dist/web/assets/git-log-panel-CJakNMDc.js.gz +0 -0
  486. package/dist/web/assets/glide-data-grid-C_SeOhSq.js.br +0 -0
  487. package/dist/web/assets/glide-data-grid-C_SeOhSq.js.gz +0 -0
  488. package/dist/web/assets/group-chat-tab--by1xCQW.js +0 -1
  489. package/dist/web/assets/group-chat-tab--by1xCQW.js.br +0 -0
  490. package/dist/web/assets/group-chat-tab--by1xCQW.js.gz +0 -0
  491. package/dist/web/assets/index-DhWjx5iR.js +0 -52
  492. package/dist/web/assets/index-DhWjx5iR.js.br +0 -0
  493. package/dist/web/assets/index-DhWjx5iR.js.gz +0 -0
  494. package/dist/web/assets/index-oIvYSKTT.css +0 -1
  495. package/dist/web/assets/index-oIvYSKTT.css.br +0 -0
  496. package/dist/web/assets/index-oIvYSKTT.css.gz +0 -0
  497. package/dist/web/assets/keybindings-store-30LYZU0l.js +0 -1
  498. package/dist/web/assets/keyboard-shortcuts-section-BgZe3QDr.js.br +0 -0
  499. package/dist/web/assets/keyboard-shortcuts-section-BgZe3QDr.js.gz +0 -0
  500. package/dist/web/assets/language-servers-section-CCMzgOaE.js.br +0 -0
  501. package/dist/web/assets/language-servers-section-CCMzgOaE.js.gz +0 -0
  502. package/dist/web/assets/lsp-client-B10suuaZ.js.br +0 -0
  503. package/dist/web/assets/lsp-client-B10suuaZ.js.gz +0 -0
  504. package/dist/web/assets/markdown-renderer-AHHFS3jc.js.br +0 -0
  505. package/dist/web/assets/markdown-renderer-AHHFS3jc.js.gz +0 -0
  506. package/dist/web/assets/markdown-renderer-DxnXtBoj.js +0 -1
  507. package/dist/web/assets/mobile-explorer-sheet-CqCsLWSr.js.br +0 -0
  508. package/dist/web/assets/mobile-explorer-sheet-CqCsLWSr.js.gz +0 -0
  509. package/dist/web/assets/new-design-dialog-B9tgnQXq.js +0 -1
  510. package/dist/web/assets/new-design-dialog-B9tgnQXq.js.br +0 -0
  511. package/dist/web/assets/new-design-dialog-B9tgnQXq.js.gz +0 -0
  512. package/dist/web/assets/notification-store-HUVzO_bp.js +0 -1
  513. package/dist/web/assets/notifications-settings-section-CXgzH_bK.js.br +0 -0
  514. package/dist/web/assets/notifications-settings-section-CXgzH_bK.js.gz +0 -0
  515. package/dist/web/assets/overview-panel-BYB8mgRE.js.br +0 -0
  516. package/dist/web/assets/overview-panel-BYB8mgRE.js.gz +0 -0
  517. package/dist/web/assets/panel-store-CWHPmHwj.js +0 -1
  518. package/dist/web/assets/panel-store-CWHPmHwj.js.br +0 -0
  519. package/dist/web/assets/panel-store-CWHPmHwj.js.gz +0 -0
  520. package/dist/web/assets/performance-panel-Dpuq3g0W.js.br +0 -0
  521. package/dist/web/assets/performance-panel-Dpuq3g0W.js.gz +0 -0
  522. package/dist/web/assets/postgres-viewer-DW0Gkmow.js.br +0 -0
  523. package/dist/web/assets/postgres-viewer-DW0Gkmow.js.gz +0 -0
  524. package/dist/web/assets/ppmbot-settings-section-DFtlGoW6.js.br +0 -0
  525. package/dist/web/assets/ppmbot-settings-section-DFtlGoW6.js.gz +0 -0
  526. package/dist/web/assets/pptx-export-CNyG32Xr.js.br +0 -0
  527. package/dist/web/assets/pptx-export-CNyG32Xr.js.gz +0 -0
  528. package/dist/web/assets/problems-panel-V5zqpuXq.js.br +0 -0
  529. package/dist/web/assets/problems-panel-V5zqpuXq.js.gz +0 -0
  530. package/dist/web/assets/process-table-DAj46HIl.js +0 -1
  531. package/dist/web/assets/process-table-DAj46HIl.js.br +0 -0
  532. package/dist/web/assets/process-table-DAj46HIl.js.gz +0 -0
  533. package/dist/web/assets/proxy-settings-section-B7AVlTfY.js.br +0 -0
  534. package/dist/web/assets/proxy-settings-section-B7AVlTfY.js.gz +0 -0
  535. package/dist/web/assets/query-audit-section-gHmRfWKr.js.br +0 -0
  536. package/dist/web/assets/query-audit-section-gHmRfWKr.js.gz +0 -0
  537. package/dist/web/assets/remote-desktop-clipboard-notice-Cgoznh8L.js +0 -1
  538. package/dist/web/assets/remote-desktop-clipboard-notice-Cgoznh8L.js.br +0 -0
  539. package/dist/web/assets/remote-desktop-clipboard-notice-Cgoznh8L.js.gz +0 -0
  540. package/dist/web/assets/remote-desktop-mobile-sheet-X12QbPhO.js +0 -2
  541. package/dist/web/assets/remote-desktop-mobile-sheet-X12QbPhO.js.br +0 -0
  542. package/dist/web/assets/remote-desktop-mobile-sheet-X12QbPhO.js.gz +0 -0
  543. package/dist/web/assets/remote-desktop-mobile-view-DsSHawSK.js +0 -1
  544. package/dist/web/assets/remote-desktop-mobile-view-DsSHawSK.js.br +0 -0
  545. package/dist/web/assets/remote-desktop-mobile-view-DsSHawSK.js.gz +0 -0
  546. package/dist/web/assets/remote-desktop-readiness-gate-CHiQEd9Q.js +0 -1
  547. package/dist/web/assets/remote-desktop-readiness-gate-CHiQEd9Q.js.br +0 -0
  548. package/dist/web/assets/remote-desktop-readiness-gate-CHiQEd9Q.js.gz +0 -0
  549. package/dist/web/assets/remote-desktop-window-content-BnznIby3.js +0 -1
  550. package/dist/web/assets/remote-desktop-window-content-BnznIby3.js.br +0 -0
  551. package/dist/web/assets/remote-desktop-window-content-BnznIby3.js.gz +0 -0
  552. package/dist/web/assets/schedules-settings-section-sYDAsBMO.js.br +0 -0
  553. package/dist/web/assets/schedules-settings-section-sYDAsBMO.js.gz +0 -0
  554. package/dist/web/assets/send-to-chat-CCdOrKc7.js.br +0 -0
  555. package/dist/web/assets/send-to-chat-CCdOrKc7.js.gz +0 -0
  556. package/dist/web/assets/services-panel-CKa2aP1d.js.br +0 -0
  557. package/dist/web/assets/services-panel-CKa2aP1d.js.gz +0 -0
  558. package/dist/web/assets/settings-body-CEIF8lkl.js +0 -2
  559. package/dist/web/assets/settings-body-CEIF8lkl.js.br +0 -0
  560. package/dist/web/assets/settings-body-CEIF8lkl.js.gz +0 -0
  561. package/dist/web/assets/settings-store-DfuiLq_9.js.br +0 -0
  562. package/dist/web/assets/settings-store-DfuiLq_9.js.gz +0 -0
  563. package/dist/web/assets/settings-tab-Du5AUPni.js +0 -1
  564. package/dist/web/assets/settings-window-content-BTflfLez.js +0 -1
  565. package/dist/web/assets/slash-command-picker-CLevA_Gf.js.br +0 -0
  566. package/dist/web/assets/slash-command-picker-CLevA_Gf.js.gz +0 -0
  567. package/dist/web/assets/sql-query-editor-Db8b0jEr.js.br +0 -0
  568. package/dist/web/assets/sql-query-editor-Db8b0jEr.js.gz +0 -0
  569. package/dist/web/assets/sqlite-viewer-VzYLMqkD.js.br +0 -0
  570. package/dist/web/assets/sqlite-viewer-VzYLMqkD.js.gz +0 -0
  571. package/dist/web/assets/system-monitor-body-BR60W_mG.js.br +0 -0
  572. package/dist/web/assets/system-monitor-body-BR60W_mG.js.gz +0 -0
  573. package/dist/web/assets/system-monitor-tab-BYpantE2.js +0 -1
  574. package/dist/web/assets/tab-host-window-content-BL61SB5m.js.br +0 -0
  575. package/dist/web/assets/tab-host-window-content-BL61SB5m.js.gz +0 -0
  576. package/dist/web/assets/terminal-tab-B4y6at4h.js.br +0 -0
  577. package/dist/web/assets/terminal-tab-B4y6at4h.js.gz +0 -0
  578. package/dist/web/assets/tool-cards-K0r_yxI0.js.br +0 -0
  579. package/dist/web/assets/tool-cards-K0r_yxI0.js.gz +0 -0
  580. package/dist/web/assets/use-inline-blame-BK23icsE.js.br +0 -0
  581. package/dist/web/assets/use-inline-blame-BK23icsE.js.gz +0 -0
  582. package/dist/web/assets/use-lsp-CRdCPCKW.js.br +0 -0
  583. package/dist/web/assets/use-lsp-CRdCPCKW.js.gz +0 -0
  584. package/dist/web/assets/use-process-kill-BWYX03KF.js.br +0 -0
  585. package/dist/web/assets/use-process-kill-BWYX03KF.js.gz +0 -0
  586. package/dist/web/assets/video-preview-DugVkMB7.js +0 -1
  587. package/dist/web/assets/video-preview-DugVkMB7.js.br +0 -0
  588. package/dist/web/assets/video-preview-DugVkMB7.js.gz +0 -0
  589. package/dist/web/assets/voice-settings-section-Ceihggqf.js.br +0 -0
  590. package/dist/web/assets/voice-settings-section-Ceihggqf.js.gz +0 -0
  591. /package/dist/web/assets/{__vite-browser-external-B_XMEguz.js → __vite-browser-external-C5mXParC.js} +0 -0
  592. /package/dist/web/assets/{agent-session-sheet-store-DO682WSg.js → agent-session-sheet-store-_OeezCXB.js} +0 -0
  593. /package/dist/web/assets/{api-settings-CpO8SPk9.js → api-settings-Cc_CpfmC.js} +0 -0
  594. /package/dist/web/assets/{api-settings-CpO8SPk9.js.br → api-settings-Cc_CpfmC.js.br} +0 -0
  595. /package/dist/web/assets/{api-settings-CpO8SPk9.js.gz → api-settings-Cc_CpfmC.js.gz} +0 -0
  596. /package/dist/web/assets/{api-themes-CJipDH_l.js → api-themes-BtMBSjuM.js} +0 -0
  597. /package/dist/web/assets/{can-open-in-ppm-Bw_xiOXX.js → can-open-in-ppm-DeE2-kd5.js} +0 -0
  598. /package/dist/web/assets/{can-open-in-ppm-Bw_xiOXX.js.br → can-open-in-ppm-DeE2-kd5.js.br} +0 -0
  599. /package/dist/web/assets/{can-open-in-ppm-Bw_xiOXX.js.gz → can-open-in-ppm-DeE2-kd5.js.gz} +0 -0
  600. /package/dist/web/assets/{csv-parser-DcRWxcNV.js → csv-parser-CoxPdyLw.js} +0 -0
  601. /package/dist/web/assets/{csv-parser-DcRWxcNV.js.br → csv-parser-CoxPdyLw.js.br} +0 -0
  602. /package/dist/web/assets/{csv-parser-DcRWxcNV.js.gz → csv-parser-CoxPdyLw.js.gz} +0 -0
  603. /package/dist/web/assets/{design-export-client-CHffN3DM.js → design-export-client-BfdZB7xK.js} +0 -0
  604. /package/dist/web/assets/{design-export-client-CHffN3DM.js.br → design-export-client-BfdZB7xK.js.br} +0 -0
  605. /package/dist/web/assets/{design-export-client-CHffN3DM.js.gz → design-export-client-BfdZB7xK.js.gz} +0 -0
  606. /package/dist/web/assets/{design-responsive-dialog-DSCpv6O1.js → design-responsive-dialog-BiYy3ibS.js} +0 -0
  607. /package/dist/web/assets/{design-responsive-dialog-DSCpv6O1.js.br → design-responsive-dialog-BiYy3ibS.js.br} +0 -0
  608. /package/dist/web/assets/{design-responsive-dialog-DSCpv6O1.js.gz → design-responsive-dialog-BiYy3ibS.js.gz} +0 -0
  609. /package/dist/web/assets/{design-system-status-label-B_0I_3a8.js → design-system-status-label-ekiLyjKn.js} +0 -0
  610. /package/dist/web/assets/{docx-preview-B3eJyY2y.js → docx-preview-Dt6RQpfA.js} +0 -0
  611. /package/dist/web/assets/{docx-preview-B3eJyY2y.js.br → docx-preview-Dt6RQpfA.js.br} +0 -0
  612. /package/dist/web/assets/{docx-preview-B3eJyY2y.js.gz → docx-preview-Dt6RQpfA.js.gz} +0 -0
  613. /package/dist/web/assets/{editor-font-BNwo7zo5.js → editor-font-CBszn6vt.js} +0 -0
  614. /package/dist/web/assets/{format-bytes-CRZfxO2J.js → format-bytes-FQmpZ25A.js} +0 -0
  615. /package/dist/web/assets/{image-gallery-DC3gRAqk.js → image-gallery-B16hO6Ab.js} +0 -0
  616. /package/dist/web/assets/{image-preview-geometry-DN3DZQp0.js → image-preview-geometry-D4Ihz4UV.js} +0 -0
  617. /package/dist/web/assets/{lib-B9k5tn9s.js → lib-6APiJkU3.js} +0 -0
  618. /package/dist/web/assets/{lib-B9k5tn9s.js.br → lib-6APiJkU3.js.br} +0 -0
  619. /package/dist/web/assets/{lib-B9k5tn9s.js.gz → lib-6APiJkU3.js.gz} +0 -0
  620. /package/dist/web/assets/{libesm-5f7T4AHA.js → libesm-5wREk7JN.js} +0 -0
  621. /package/dist/web/assets/{libesm-5f7T4AHA.js.br → libesm-5wREk7JN.js.br} +0 -0
  622. /package/dist/web/assets/{libesm-5f7T4AHA.js.gz → libesm-5wREk7JN.js.gz} +0 -0
  623. /package/dist/web/assets/{media-url-CCGOIxzS.js → media-url-CrT-qulZ.js} +0 -0
  624. /package/dist/web/assets/{metric-chart-canvas-CjFbZuhk.js → metric-chart-canvas-D7f-LQ8x.js} +0 -0
  625. /package/dist/web/assets/{metric-chart-canvas-CjFbZuhk.js.br → metric-chart-canvas-D7f-LQ8x.js.br} +0 -0
  626. /package/dist/web/assets/{metric-chart-canvas-CjFbZuhk.js.gz → metric-chart-canvas-D7f-LQ8x.js.gz} +0 -0
  627. /package/dist/web/assets/{monaco-dotenv-language-Bwq3gQsi.js → monaco-dotenv-language-DbSss7YR.js} +0 -0
  628. /package/dist/web/assets/{onboarding-entry-CuH1Kqwu.js → onboarding-entry-CGFdGLLP.js} +0 -0
  629. /package/dist/web/assets/{onboarding-entry-CuH1Kqwu.js.br → onboarding-entry-CGFdGLLP.js.br} +0 -0
  630. /package/dist/web/assets/{onboarding-entry-CuH1Kqwu.js.gz → onboarding-entry-CGFdGLLP.js.gz} +0 -0
  631. /package/dist/web/assets/{pdf-preview-X8bdpfE9.js → pdf-preview-B8A4T-ES.js} +0 -0
  632. /package/dist/web/assets/{pdf-preview-X8bdpfE9.js.br → pdf-preview-B8A4T-ES.js.br} +0 -0
  633. /package/dist/web/assets/{pdf-preview-X8bdpfE9.js.gz → pdf-preview-B8A4T-ES.js.gz} +0 -0
  634. /package/dist/web/assets/{separator-DMu7VFQa.js → separator-dHj3Km--.js} +0 -0
  635. /package/dist/web/assets/{sortable-header-CipIP7aH.js → sortable-header-BOa8o7NJ.js} +0 -0
  636. /package/dist/web/assets/{sortable-header-CipIP7aH.js.br → sortable-header-BOa8o7NJ.js.br} +0 -0
  637. /package/dist/web/assets/{sortable-header-CipIP7aH.js.gz → sortable-header-BOa8o7NJ.js.gz} +0 -0
  638. /package/dist/web/assets/{use-blob-url-DjuRQWnR.js → use-blob-url-BC69n15x.js} +0 -0
  639. /package/dist/web/assets/{vendor-xterm-Be4L1NHd.js → vendor-xterm-CfasyE0m.js} +0 -0
  640. /package/dist/web/assets/{vendor-xterm-Be4L1NHd.js.br → vendor-xterm-CfasyE0m.js.br} +0 -0
  641. /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 and invalidated on file changes.
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, readFileSync } from "node:fs";
9
- import { resolve, relative, join, sep } from "node:path";
10
- import ignore, { type Ignore } from "ignore";
11
- import type { FileEntry, FileDirEntry } from "../types/project.ts";
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
- const indexCache = new Map<string, FileEntry[]>();
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
- /** Invalidate cached flat index for a project (called on file change events) */
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
- // Gitignore loader (shared utility)
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 loadGitignore(projectPath: string): Ignore {
36
- const ig = ignore();
37
- const gitignorePath = join(projectPath, ".gitignore");
38
- if (existsSync(gitignorePath)) {
39
- try {
40
- const content = readFileSync(gitignorePath, "utf-8");
41
- ig.add(content);
42
- } catch { /* unreadable — skip */ }
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
- * Build flat index of all files in project for palette/search.
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
- * Result is cached; call invalidateIndexCache(projectPath) to bust.
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): FileEntry[] {
154
- const cached = indexCache.get(projectPath);
155
- if (cached) return cached;
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
- const filter = resolveFilter(projectPath);
158
- const ig = filter.useIgnoreFiles ? loadGitignore(projectPath) : null;
159
- const allExclude = [...filter.filesExclude, ...filter.searchExclude];
160
-
161
- const entries: FileEntry[] = [];
162
- walkForIndex(projectPath, projectPath, allExclude, ig, entries);
163
-
164
- indexCache.set(projectPath, entries);
165
- return entries;
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
- if (entry.isDirectory()) {
198
- results.push({ path: relPosix, name: entry.name, type: "directory" });
199
- walkForIndex(rootPath, fullPath, allExclude, ig, results);
200
- } else {
201
- results.push({ path: relPosix, name: entry.name, type: "file", ...(isIgnored && { isIgnored: true }) });
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, type FSWatcher } from "node:fs";
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: FSWatcher;
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` handles held. */
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
- // Only Bun on Linux has the stale-watch defect; elsewhere re-watching works
146
- // and paying for polling would be pure waste.
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);