@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
@@ -0,0 +1,387 @@
1
+ /**
2
+ * Installing an APK on a running emulator.
3
+ *
4
+ * `EmulatorController` has no install RPC — this is `adb install`, and that is the whole reason
5
+ * the surface is a *typed* one (plan ADR: "API typed riêng, không mở generic shell/RPC proxy").
6
+ * The caller names a device and a file; nothing here takes a command, a flag or an argument
7
+ * string from the browser.
8
+ *
9
+ * Two things this file exists to get right:
10
+ *
11
+ * - **The right device.** Measured with two emulators attached, `adb install` with no `-s`
12
+ * answers **"adb: more than one device/emulator"** and installs nothing — so the failure mode
13
+ * is a refusal rather than the coin toss it looks like, and on a single-device host it would
14
+ * work by luck and break the day a second emulator is started. The serial is always passed,
15
+ * and `tests/e2e/android-two-device-install-e2e.ts` proves it is honoured rather than merely
16
+ * present, by installing to one of two live devices and checking both.
17
+ * - **The real reason it failed.** adb reports `INSTALL_FAILED_NO_MATCHING_ABIS` and friends on
18
+ * stdout with exit code 0 in some versions, so the exit code alone is not the answer, and the
19
+ * raw code alone is not something to show a person.
20
+ */
21
+ import { existsSync, statSync } from "node:fs";
22
+
23
+ /** A larger APK than this is almost certainly not what the user meant to upload. */
24
+ export const MAX_APK_BYTES = 2 * 1024 * 1024 * 1024;
25
+
26
+ /**
27
+ * How long one `adb install` may take before it is killed (plan §7: "deadline").
28
+ *
29
+ * Generous on purpose — a cold guest installing a 300 MB APK is minutes of real work — but not
30
+ * unbounded: adb against a half-wedged emulator hangs rather than erroring, and an install that
31
+ * never ends leaves an operation stuck at "running" forever with no way out but a restart.
32
+ */
33
+ export const INSTALL_DEADLINE_MS = 10 * 60_000;
34
+
35
+ /**
36
+ * Total bytes the staging directory may hold at once.
37
+ *
38
+ * `MAX_APK_BYTES` bounds one upload; nothing bounds ten at once, and a disk full of half-uploaded
39
+ * APKs takes the whole PPM directory down with it — the database included.
40
+ */
41
+ export const MAX_STAGING_BYTES = 4 * 1024 * 1024 * 1024;
42
+
43
+ export interface InstallOptions {
44
+ adbPath: string;
45
+ /** The emulator's adb serial, e.g. `emulator-5554`. Never omitted. */
46
+ serial: string;
47
+ apkPath: string;
48
+ /** Replace an existing install rather than failing on a duplicate package. */
49
+ reinstall?: boolean;
50
+ /** Allow installing over a newer version, which adb otherwise refuses. */
51
+ allowDowngrade?: boolean;
52
+ onProgress?: (line: string) => void;
53
+ signal?: AbortSignal;
54
+ /** Overrides `INSTALL_DEADLINE_MS`; a test uses it to reach the timeout in a second. */
55
+ deadlineMs?: number;
56
+ }
57
+
58
+ export interface InstallResult {
59
+ ok: boolean;
60
+ /** A sentence to show a person, already explaining what to do about it. */
61
+ message: string;
62
+ /** adb's own failure code, when it gave one — worth keeping for a bug report. */
63
+ code: string | null;
64
+ output: string;
65
+ }
66
+
67
+ /**
68
+ * adb's failure codes, in terms that say what to do. Anything not here is passed through
69
+ * verbatim rather than flattened into "install failed", which would hide the one useful word.
70
+ */
71
+ const FAILURES: { code: string; message: string }[] = [
72
+ { code: "INSTALL_FAILED_NO_MATCHING_ABIS",
73
+ message: "the APK has no native code for this emulator's ABI — use an x86_64 build, or an AVD whose ABI matches the APK" },
74
+ { code: "INSTALL_FAILED_OLDER_SDK",
75
+ message: "the APK needs a newer Android than this AVD runs — start an AVD with a higher API level" },
76
+ { code: "INSTALL_FAILED_UPDATE_INCOMPATIBLE",
77
+ message: "a package with this name is already installed and was signed with a different key — uninstall it first" },
78
+ { code: "INSTALL_FAILED_VERSION_DOWNGRADE",
79
+ message: "the installed version is newer than this APK — turn on 'allow downgrade' to replace it" },
80
+ { code: "INSTALL_FAILED_INSUFFICIENT_STORAGE",
81
+ message: "the emulator has no room left — wipe its data or give the AVD a larger disk" },
82
+ { code: "INSTALL_FAILED_INVALID_APK",
83
+ message: "adb could not read this file as an APK — check it is not a partial download or an AAB" },
84
+ { code: "INSTALL_PARSE_FAILED_NO_CERTIFICATES",
85
+ message: "the APK is not signed — sign it, or use a debug build" },
86
+ { code: "INSTALL_FAILED_TEST_ONLY",
87
+ message: "this is a test-only build — rebuild it without `testOnly`, or install it with adb's own `-t`" },
88
+ { code: "device offline",
89
+ message: "the emulator stopped responding to adb — wait for it to finish booting, or restart it" },
90
+ ];
91
+
92
+ /** Turn adb's output into something worth showing, without throwing away what it said. */
93
+ export function explainInstallOutput(output: string, exitCode: number): { ok: boolean; message: string; code: string | null } {
94
+ const text = output.trim();
95
+ const failure = FAILURES.find((f) => text.includes(f.code));
96
+ if (failure) return { ok: false, message: failure.message, code: failure.code };
97
+
98
+ // Some adb versions print `Failure [CODE]` for codes this table has never heard of.
99
+ const unknown = text.match(/Failure\s*\[([A-Z_]+[^\]]*)\]/);
100
+ if (unknown) return { ok: false, message: `adb refused the install: ${unknown[1]}`, code: unknown[1] ?? null };
101
+
102
+ // `Success` on stdout is adb's own word for it, and is more reliable than the exit code:
103
+ // several adb versions exit 0 on a failed install.
104
+ if (text.split("\n").some((l) => l.trim() === "Success")) {
105
+ return { ok: true, message: "installed", code: null };
106
+ }
107
+ if (exitCode === 0) return { ok: true, message: "installed", code: null };
108
+ return { ok: false, message: text || `adb exited with code ${exitCode}`, code: null };
109
+ }
110
+
111
+ export async function installApk(opts: InstallOptions): Promise<InstallResult> {
112
+ if (!existsSync(opts.apkPath)) {
113
+ return { ok: false, message: "that file no longer exists", code: null, output: "" };
114
+ }
115
+ const size = statSync(opts.apkPath).size;
116
+ if (size === 0) return { ok: false, message: "that file is empty", code: null, output: "" };
117
+ if (size > MAX_APK_BYTES) {
118
+ return { ok: false, message: `that file is ${(size / 1024 / 1024).toFixed(0)} MB, past the ${MAX_APK_BYTES / 1024 / 1024 / 1024} GB limit`, code: null, output: "" };
119
+ }
120
+
121
+ const args = ["-s", opts.serial, "install"];
122
+ if (opts.reinstall !== false) args.push("-r");
123
+ if (opts.allowDowngrade) args.push("-d");
124
+ args.push(opts.apkPath);
125
+
126
+ // Cancelled before it started: adb is never run at all.
127
+ if (opts.signal?.aborted) return { ok: false, message: "cancelled", code: null, output: "" };
128
+
129
+ let proc: ReturnType<typeof Bun.spawn>;
130
+ try {
131
+ proc = Bun.spawn([opts.adbPath, ...args], { stdout: "pipe", stderr: "pipe", windowsHide: true });
132
+ } catch (e) {
133
+ // Bun.spawn throws synchronously when the binary is missing, and the throw escapes an
134
+ // awaited try placed further out (CLAUDE.md, host-info/spawn-runner.ts).
135
+ return { ok: false, message: `could not run adb: ${(e as Error).message}`, code: null, output: "" };
136
+ }
137
+
138
+ // Cancel has the same hazard as the deadline below — killing the process may not close its
139
+ // pipes — so it resolves the race itself rather than relying on the read ending.
140
+ let resolveAborted: (() => void) | null = null;
141
+ const aborted = new Promise<"aborted">((resolve) => { resolveAborted = () => resolve("aborted"); });
142
+ const onAbort = () => {
143
+ try { proc.kill(); } catch { /* already gone */ }
144
+ resolveAborted?.();
145
+ };
146
+ opts.signal?.addEventListener("abort", onAbort, { once: true });
147
+
148
+ /**
149
+ * Reading the pipes to the end is the *normal* path, not a reliable one.
150
+ *
151
+ * Measured: killing the process does **not** necessarily close its pipes — a child that
152
+ * inherited them keeps them open, and `reader.read()` then never resolves. So the deadline
153
+ * races this rather than trusting the kill to unblock it, the same shape `Encoder.stop()` in
154
+ * `android-video.ts` needed for ffmpeg. `.catch` on the handle keeps an abandoned read from
155
+ * surfacing as an unhandled rejection after this function has already answered.
156
+ */
157
+ const collected = (async () => {
158
+ const [out, errOut] = await Promise.all([
159
+ readStream(proc.stdout, opts.onProgress),
160
+ readStream(proc.stderr, opts.onProgress),
161
+ ]);
162
+ return { output: `${out}${errOut}`.trim(), exitCode: await proc.exited };
163
+ })();
164
+ collected.catch(() => { /* the deadline path abandons this deliberately */ });
165
+
166
+ const deadlineMs = opts.deadlineMs ?? INSTALL_DEADLINE_MS;
167
+ let deadlineTimer: ReturnType<typeof setTimeout> | undefined;
168
+ const expired = new Promise<"expired">((resolve) => {
169
+ deadlineTimer = setTimeout(() => {
170
+ try { proc.kill(); } catch { /* already gone */ }
171
+ resolve("expired");
172
+ }, deadlineMs);
173
+ });
174
+
175
+ try {
176
+ const outcome = await Promise.race([collected, expired, aborted]);
177
+ if (outcome === "aborted") {
178
+ return { ok: false, message: "cancelled", code: null, output: "" };
179
+ }
180
+ if (outcome === "expired") {
181
+ const minutes = deadlineMs >= 60_000 ? `${Math.round(deadlineMs / 60_000)} minutes` : `${Math.round(deadlineMs / 1000)}s`;
182
+ return {
183
+ ok: false,
184
+ message: `adb did not finish within ${minutes} — the emulator may be wedged`,
185
+ code: null,
186
+ // Whatever it managed to say, if it closed its pipes on the way out. Never waited for.
187
+ output: await Promise.race([collected.then((c) => c.output).catch(() => ""), Bun.sleep(300).then(() => "")]),
188
+ };
189
+ }
190
+ if (opts.signal?.aborted) {
191
+ return { ok: false, message: "cancelled", code: null, output: outcome.output };
192
+ }
193
+ return { ...explainInstallOutput(outcome.output, outcome.exitCode), output: outcome.output };
194
+ } finally {
195
+ clearTimeout(deadlineTimer);
196
+ opts.signal?.removeEventListener("abort", onAbort);
197
+ }
198
+ }
199
+
200
+ /** Manual pull loop, not `for await` — see the note in `android-video.ts`. */
201
+ async function readStream(
202
+ stream: ReadableStream<Uint8Array> | number | undefined,
203
+ onLine?: (line: string) => void,
204
+ ): Promise<string> {
205
+ if (!stream || typeof stream === "number") return "";
206
+ const reader = stream.getReader();
207
+ const decoder = new TextDecoder();
208
+ let text = "";
209
+ for (;;) {
210
+ const { done, value } = await reader.read();
211
+ if (done) return text;
212
+ if (!value) continue;
213
+ const chunk = decoder.decode(value, { stream: true });
214
+ text += chunk;
215
+ if (onLine) for (const line of chunk.split("\n")) { const t = line.trim(); if (t) onLine(t); }
216
+ }
217
+ }
218
+
219
+ /* ---------------------------------------------------------------------------------------------
220
+ * Staging an uploaded APK.
221
+ *
222
+ * The plan's Phase 3 gate is "hủy upload không để temp" — a cancelled upload leaves nothing
223
+ * behind. That is not automatic: an aborted request rejects the read *mid-file*, so the partial
224
+ * has to be deleted on the way out of every failure path, and a PPM killed mid-upload leaves one
225
+ * behind that only a later sweep can remove.
226
+ * ------------------------------------------------------------------------------------------- */
227
+
228
+ import { mkdirSync, readdirSync, rmSync, statSync as statFile } from "node:fs";
229
+ import { join, relative, sep } from "node:path";
230
+ import { getPpmDir } from "../ppm-dir.ts";
231
+
232
+ export function apkStagingDir(): string {
233
+ return join(getPpmDir(), "android", "apk-staging");
234
+ }
235
+
236
+ /** A staged file older than this is from a PPM that died mid-upload. */
237
+ const STAGING_MAX_AGE_MS = 60 * 60_000;
238
+
239
+ /**
240
+ * Called before each upload. What a crash left behind is removed by the next upload once it is an
241
+ * hour old, and until then counts against `MAX_STAGING_BYTES`, so crashes cannot pile up gigabytes.
242
+ */
243
+ export function sweepApkStaging(): number {
244
+ let removed = 0;
245
+ let names: string[];
246
+ try { names = readdirSync(apkStagingDir()); } catch { return 0; }
247
+ for (const name of names) {
248
+ const path = join(apkStagingDir(), name);
249
+ try {
250
+ if (Date.now() - statFile(path).mtimeMs < STAGING_MAX_AGE_MS) continue;
251
+ rmSync(path, { force: true });
252
+ removed++;
253
+ } catch { /* another sweep got there first */ }
254
+ }
255
+ return removed;
256
+ }
257
+
258
+ /** What the staging directory currently holds, for the quota check. */
259
+ export function stagingBytes(): number {
260
+ let total = 0;
261
+ try {
262
+ for (const name of readdirSync(apkStagingDir())) {
263
+ try { total += statFile(join(apkStagingDir(), name)).size; } catch { /* gone */ }
264
+ }
265
+ } catch { /* no directory yet */ }
266
+ return total;
267
+ }
268
+
269
+ export interface StagedApk {
270
+ path: string;
271
+ bytes: number;
272
+ /** Idempotent — safe to call after the install, and again on an error path. */
273
+ discard(): void;
274
+ }
275
+
276
+ /**
277
+ * Write an upload to a staging file, refusing anything that is not a zip (an APK is one) and
278
+ * anything past the size limit — both checked *while* streaming, so a 5 GB body is cut off at
279
+ * the limit rather than after it has all been written.
280
+ */
281
+ export async function stageApkUpload(
282
+ body: ReadableStream<Uint8Array>,
283
+ opts: { signal?: AbortSignal; maxBytes?: number } = {},
284
+ ): Promise<StagedApk> {
285
+ sweepApkStaging();
286
+ const alreadyStaged = stagingBytes();
287
+ if (alreadyStaged >= MAX_STAGING_BYTES) {
288
+ throw new Error("another upload is still using the staging space — wait for it to finish");
289
+ }
290
+ mkdirSync(apkStagingDir(), { recursive: true });
291
+ const path = join(apkStagingDir(), `${crypto.randomUUID()}.apk`);
292
+ const discard = () => { try { rmSync(path, { force: true }); } catch { /* already gone */ } };
293
+
294
+ const limit = opts.maxBytes ?? MAX_APK_BYTES;
295
+ const sink = Bun.file(path).writer();
296
+ const reader = body.getReader();
297
+ let bytes = 0;
298
+ let head = new Uint8Array(0);
299
+
300
+ try {
301
+ for (;;) {
302
+ if (opts.signal?.aborted) throw new Error("cancelled");
303
+ const { done, value } = await reader.read();
304
+ if (done) break;
305
+ if (!value) continue;
306
+
307
+ if (head.length < 4) {
308
+ const merged = new Uint8Array(head.length + value.length);
309
+ merged.set(head); merged.set(value, head.length);
310
+ head = merged.slice(0, 4);
311
+ if (head.length >= 2 && !(head[0] === 0x50 && head[1] === 0x4b)) {
312
+ throw new Error("that file is not an APK (no zip header)");
313
+ }
314
+ }
315
+
316
+ bytes += value.length;
317
+ if (bytes > limit) throw new Error(`the upload is past the ${limit / 1024 / 1024 / 1024} GB limit`);
318
+ // The quota counts everything staged, not only this upload: two concurrent 2 GB uploads
319
+ // each stay under their own limit and together fill the disk the database lives on.
320
+ if (alreadyStaged + bytes > MAX_STAGING_BYTES) {
321
+ throw new Error("there is not enough staging space for this upload right now");
322
+ }
323
+ sink.write(value);
324
+ }
325
+ await sink.end();
326
+ } catch (e) {
327
+ try { await sink.end(); } catch { /* the writer may already be broken */ }
328
+ discard();
329
+ try { await reader.cancel(); } catch { /* the peer is gone */ }
330
+ throw e;
331
+ }
332
+
333
+ if (bytes === 0) { discard(); throw new Error("the upload was empty"); }
334
+ return { path, bytes, discard };
335
+ }
336
+
337
+ /* ---------------------------------------------------------------------------------------------
338
+ * Finding the APKs a project has already built.
339
+ *
340
+ * A full walk of a project root is not acceptable here — the same reasoning as the recursive
341
+ * `fs.watch` ban in CLAUDE.md: `node_modules` alone is tens of thousands of directories. This is
342
+ * depth-bounded and prunes by name, which is enough because Gradle always writes to
343
+ * a `build/outputs/apk` directory.
344
+ * ------------------------------------------------------------------------------------------- */
345
+
346
+ const PRUNED_DIRS = new Set([
347
+ "node_modules", ".git", ".gradle", ".idea", "vendor", "target", "Pods",
348
+ ".venv", "venv", "__pycache__", ".next", ".cache", "dist",
349
+ ]);
350
+ const MAX_WALK_DEPTH = 8;
351
+ const MAX_APKS_LISTED = 200;
352
+
353
+ export interface ProjectApk {
354
+ /** Project-relative, which is what the install route takes. */
355
+ path: string;
356
+ bytes: number;
357
+ modifiedAt: number;
358
+ }
359
+
360
+ export async function findProjectApks(root: string): Promise<ProjectApk[]> {
361
+ const { readdir, stat } = await import("node:fs/promises");
362
+ const found: ProjectApk[] = [];
363
+
364
+ async function walk(dir: string, depth: number): Promise<void> {
365
+ if (depth > MAX_WALK_DEPTH || found.length >= MAX_APKS_LISTED) return;
366
+ let entries;
367
+ try { entries = await readdir(dir, { withFileTypes: true }); } catch { return; }
368
+ for (const entry of entries) {
369
+ if (found.length >= MAX_APKS_LISTED) return;
370
+ const full = join(dir, entry.name);
371
+ if (entry.isDirectory()) {
372
+ if (PRUNED_DIRS.has(entry.name) || entry.name.startsWith(".")) continue;
373
+ await walk(full, depth + 1);
374
+ } else if (entry.isFile() && entry.name.toLowerCase().endsWith(".apk")) {
375
+ try {
376
+ const s = await stat(full);
377
+ // Relative and "/"-separated on every OS, like every other path PPM hands the browser.
378
+ found.push({ path: relative(root, full).split(sep).join("/"), bytes: s.size, modifiedAt: s.mtimeMs });
379
+ } catch { /* it went away between the listing and the stat */ }
380
+ }
381
+ }
382
+ }
383
+
384
+ await walk(root, 0);
385
+ // Newest first: after a build, the one the user wants is the one that just appeared.
386
+ return found.sort((a, b) => b.modifiedAt - a.modifiedAt);
387
+ }
@@ -0,0 +1,154 @@
1
+ /**
2
+ * "Can this host run an Android emulator, and if not, exactly what is missing?"
3
+ *
4
+ * Every negative answer carries a reason and, where one exists, the command that fixes it. Plan
5
+ * §6: the empty state must distinguish no SDK, no emulator/system image, no AVD, and unusable
6
+ * acceleration — never an indefinite spinner.
7
+ */
8
+ import { discoverSdk, resolveAvdHome, type AndroidSdk } from "./sdk-discovery.ts";
9
+ import { listAvds } from "./avd-list.ts";
10
+ import { workingEncoders } from "../media-transcode/ffmpeg-capabilities.ts";
11
+
12
+ export interface AndroidRequirement {
13
+ id: string;
14
+ label: string;
15
+ met: boolean;
16
+ detail: string;
17
+ /** A command the user can run to fix it, when one exists. Never executed by PPM. */
18
+ fix: string | null;
19
+ }
20
+
21
+ export interface AndroidCapabilities {
22
+ enabled: boolean;
23
+ sdk: AndroidSdk;
24
+ avdCount: number;
25
+ /** H.264 encoders that really encode on this host, preference order. */
26
+ encoders: string[];
27
+ accelerationOk: boolean;
28
+ accelerationDetail: string;
29
+ requirements: AndroidRequirement[];
30
+ ready: boolean;
31
+ }
32
+
33
+ /**
34
+ * `emulator -accel-check` reports whether the host's hypervisor is usable. Without it an
35
+ * emulator still "starts" and then runs at a speed nobody would call working, so this is a
36
+ * requirement rather than a hint.
37
+ */
38
+ async function checkAcceleration(emulatorPath: string | null): Promise<{ ok: boolean; detail: string }> {
39
+ if (!emulatorPath) return { ok: false, detail: "no emulator binary" };
40
+ try {
41
+ const proc = Bun.spawn([emulatorPath, "-accel-check"], { stdout: "pipe", stderr: "pipe", windowsHide: true });
42
+ const [out, errOut] = await Promise.all([
43
+ new Response(proc.stdout).text(),
44
+ new Response(proc.stderr).text(),
45
+ ]);
46
+ const code = await proc.exited;
47
+ // `-accel-check` prints a marker line, an exit code, the human-readable verdict, then the
48
+ // marker again — e.g. "accel:", "0", "KVM (version 12) is installed and usable.", "accel".
49
+ // Taking the last line yields the bare marker, so pick the sentence instead.
50
+ const lines = `${out}${errOut}`.split("\n").map((l) => l.trim()).filter(Boolean);
51
+ const sentence = lines.filter((l) => l !== "accel" && l !== "accel:" && !/^-?\d+$/.test(l))
52
+ .sort((a, b) => b.length - a.length)[0];
53
+ return { ok: code === 0, detail: sentence || (code === 0 ? "usable" : "not usable") };
54
+ } catch {
55
+ // Bun.spawn throws synchronously when the binary is missing.
56
+ return { ok: false, detail: "emulator binary could not be run" };
57
+ }
58
+ }
59
+
60
+ export async function androidCapabilities(
61
+ opts: { enabled: boolean; configuredSdkRoot?: string | null },
62
+ ): Promise<AndroidCapabilities> {
63
+ // Off — the default — answers without looking at the SDK at all. Every page load asks (the nav
64
+ // rail and the mobile drawer decide from `enabled` whether to show the entry), and looking runs
65
+ // `emulator` twice, `adb` and two JVMs (`avdmanager`, `sdkmanager`) on a host that has an SDK.
66
+ if (!opts.enabled) return disabledCapabilities();
67
+
68
+ const sdk = await discoverSdk(opts.configuredSdkRoot ?? null);
69
+ const avds = sdk.root ? listAvds(sdk.avdHome) : [];
70
+ const [accel, encoders] = await Promise.all([
71
+ checkAcceleration(sdk.emulator.path),
72
+ workingEncoders(),
73
+ ]);
74
+
75
+ const requirements: AndroidRequirement[] = [
76
+ {
77
+ id: "sdk",
78
+ label: "Android SDK",
79
+ met: !!sdk.root,
80
+ detail: sdk.root ? `${sdk.root} (found via ${sdk.source})` : "no SDK found",
81
+ fix: sdk.root ? null : "Install Android Studio, or set ANDROID_HOME to an existing SDK",
82
+ },
83
+ {
84
+ id: "emulator",
85
+ label: "Emulator",
86
+ met: !!sdk.emulator.path,
87
+ detail: sdk.emulator.version ?? "not installed",
88
+ fix: sdk.emulator.path ? null : "sdkmanager --install emulator",
89
+ },
90
+ {
91
+ id: "adb",
92
+ label: "adb",
93
+ met: !!sdk.adb.path,
94
+ detail: sdk.adb.version ?? "not installed",
95
+ fix: sdk.adb.path ? null : "sdkmanager --install platform-tools",
96
+ },
97
+ {
98
+ id: "acceleration",
99
+ label: "Hardware acceleration",
100
+ met: accel.ok,
101
+ detail: accel.detail,
102
+ fix: accel.ok ? null : "Enable virtualisation in the BIOS, and KVM/HAXM/Hypervisor.Framework for your OS",
103
+ },
104
+ {
105
+ id: "avd",
106
+ label: "At least one AVD",
107
+ met: avds.length > 0,
108
+ detail: avds.length ? `${avds.length} available` : "none created yet",
109
+ fix: avds.length ? null : "Create one in Android Studio's Device Manager, or with avdmanager",
110
+ },
111
+ {
112
+ id: "encoder",
113
+ label: "H.264 encoder",
114
+ met: encoders.length > 0,
115
+ detail: encoders.length ? encoders.join(", ") : "ffmpeg has no working H.264 encoder here",
116
+ fix: encoders.length ? null : "Install ffmpeg with libx264, or a VAAPI/QSV/NVENC-capable build",
117
+ },
118
+ ];
119
+
120
+ return {
121
+ enabled: opts.enabled,
122
+ sdk,
123
+ avdCount: avds.length,
124
+ encoders,
125
+ accelerationOk: accel.ok,
126
+ accelerationDetail: accel.detail,
127
+ requirements,
128
+ ready: opts.enabled && requirements.every((r) => r.met),
129
+ };
130
+ }
131
+
132
+ function disabledCapabilities(): AndroidCapabilities {
133
+ const none = { path: null, version: null };
134
+ const avdHome = resolveAvdHome();
135
+ return {
136
+ enabled: false,
137
+ sdk: {
138
+ root: null, source: "not-found", emulator: none, adb: none, avdmanager: none, sdkmanager: none,
139
+ avdHome: avdHome.dir, avdHomeFromEnv: avdHome.fromEnv,
140
+ },
141
+ avdCount: 0,
142
+ encoders: [],
143
+ accelerationOk: false,
144
+ accelerationDetail: "not checked while Android emulator support is off",
145
+ requirements: [{
146
+ id: "enabled",
147
+ label: "Android emulator support",
148
+ met: false,
149
+ detail: "off on this host: it runs emulators, so it has to be asked for",
150
+ fix: "Start PPM with ANDROID_EMULATOR_ENABLED=1",
151
+ }],
152
+ ready: false,
153
+ };
154
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Feature switch. Android emulator support is **off by default** — unlike Remote Desktop, which
3
+ * ships on, because this surface spawns multi-gigabyte emulator processes on the host and most
4
+ * PPM installs have no Android SDK at all. `ANDROID_EMULATOR_ENABLED=1` (or `true`) turns it on.
5
+ *
6
+ * Every entry point (REST routes, WS upgrade) checks this independently rather than trusting a
7
+ * single call site to stay correct as routing evolves — same rule as `remote-desktop-flag.ts`.
8
+ */
9
+ export function isAndroidEmulatorEnabled(): boolean {
10
+ const v = process.env.ANDROID_EMULATOR_ENABLED?.trim().toLowerCase();
11
+ return v === "1" || v === "true";
12
+ }
@@ -0,0 +1,124 @@
1
+ /**
2
+ * The EmulatorController gRPC channel.
3
+ *
4
+ * Two rules this file exists to enforce, both of them measured in Phase 0:
5
+ *
6
+ * - **Never `protoLoader.loadSync()`.** It reads the .proto off disk at call time, and in a
7
+ * compiled PPM that path resolves under `file:///$bunfs/root/` and throws ENOENT. The
8
+ * descriptor is generated at build time (`scripts/gen-android-proto.ts`) and imported, so the
9
+ * bundler embeds it. Same trap family as `bundledServerEntry` in CLAUDE.md.
10
+ * - **Never dial anything but loopback.** The endpoint comes from the emulator's own discovery
11
+ * file and nothing else; the browser can neither supply a gRPC URL nor name a host.
12
+ *
13
+ * grpc-js refuses call credentials on an insecure channel ("Cannot create secure credentials
14
+ * with an insecure channel"), so the bearer token travels as per-call metadata instead. That is
15
+ * safe here precisely because the channel never leaves 127.0.0.1.
16
+ */
17
+ import * as grpc from "@grpc/grpc-js";
18
+ import * as protoLoader from "@grpc/proto-loader";
19
+ import { emulatorControllerDescriptor } from "./proto/emulator-controller-descriptor.ts";
20
+ import type { RunningEmulator } from "./emulator-discovery.ts";
21
+
22
+ /** A full-resolution RGB888 frame at 1080x2400 is 7.8 MB; grpc-js caps receives at 4 MB by
23
+ * default and would fail the stream with RESOURCE_EXHAUSTED without naming the size. */
24
+ const MAX_RECEIVE_BYTES = 64 * 1024 * 1024;
25
+
26
+ const DEFAULT_DEADLINE_MS = 5_000;
27
+
28
+ let cachedCtor: any = null;
29
+
30
+ function controllerCtor(): any {
31
+ if (cachedCtor) return cachedCtor;
32
+ const def = protoLoader.fromJSON(
33
+ emulatorControllerDescriptor as unknown as Parameters<typeof protoLoader.fromJSON>[0],
34
+ {
35
+ keepCase: true,
36
+ longs: String,
37
+ enums: String,
38
+ defaults: true,
39
+ oneofs: true,
40
+ // Buffer, not base64: the screenshot path must not pay a string round trip per frame.
41
+ bytes: Buffer,
42
+ },
43
+ );
44
+ const pkg = grpc.loadPackageDefinition(def) as any;
45
+ cachedCtor = pkg.android.emulation.control.EmulatorController;
46
+ return cachedCtor;
47
+ }
48
+
49
+ export interface EmulatorChannel {
50
+ readonly client: any;
51
+ readonly metadata: grpc.Metadata;
52
+ readonly target: string;
53
+ close(): void;
54
+ }
55
+
56
+ export function connectToEmulator(emulator: RunningEmulator): EmulatorChannel {
57
+ const target = `127.0.0.1:${emulator.grpcPort}`;
58
+ const client = new (controllerCtor())(target, grpc.credentials.createInsecure(), {
59
+ "grpc.max_receive_message_length": MAX_RECEIVE_BYTES,
60
+ // Keepalive so a wedged emulator surfaces as a broken channel instead of a silent stall.
61
+ "grpc.keepalive_time_ms": 20_000,
62
+ "grpc.keepalive_timeout_ms": 5_000,
63
+ "grpc.keepalive_permit_without_calls": 1,
64
+ });
65
+
66
+ const metadata = new grpc.Metadata();
67
+ if (emulator.grpcToken) metadata.set("authorization", `Bearer ${emulator.grpcToken}`);
68
+
69
+ return {
70
+ client,
71
+ metadata,
72
+ target,
73
+ close: () => grpc.closeClient(client),
74
+ };
75
+ }
76
+
77
+ /** One unary call with a deadline, promisified. A hung emulator can never wedge a caller. */
78
+ export function unaryCall<T = any>(
79
+ channel: EmulatorChannel,
80
+ method: string,
81
+ arg: unknown = {},
82
+ timeoutMs = DEFAULT_DEADLINE_MS,
83
+ ): Promise<T> {
84
+ return new Promise((resolve, reject) => {
85
+ const deadline = new Date(Date.now() + timeoutMs);
86
+ channel.client[method](arg, channel.metadata, { deadline }, (e: Error | null, res: T) => {
87
+ if (e) reject(e); else resolve(res);
88
+ });
89
+ });
90
+ }
91
+
92
+ export interface EmulatorStatus {
93
+ booted: boolean;
94
+ uptimeMs: number;
95
+ displayWidth: number | null;
96
+ displayHeight: number | null;
97
+ displayDensity: number | null;
98
+ }
99
+
100
+ function hardwareEntry(status: any, key: string): string | undefined {
101
+ return (status?.hardwareConfig?.entry ?? []).find((e: any) => e.key === key)?.value;
102
+ }
103
+
104
+ export async function getEmulatorStatus(channel: EmulatorChannel): Promise<EmulatorStatus> {
105
+ const s = await unaryCall(channel, "getStatus");
106
+ const num = (k: string) => {
107
+ const v = Number(hardwareEntry(s, k));
108
+ return Number.isFinite(v) && v > 0 ? v : null;
109
+ };
110
+ return {
111
+ booted: !!s.booted,
112
+ uptimeMs: Number(s.uptime ?? 0),
113
+ displayWidth: num("hw.lcd.width"),
114
+ displayHeight: num("hw.lcd.height"),
115
+ displayDensity: num("hw.lcd.density"),
116
+ };
117
+ }
118
+
119
+ /** Ask the VM to shut down. Graceful: measured, the process is gone in 1-3 seconds, so nothing
120
+ * here ever needs to signal a pid — and never by process *name*, which on this very host
121
+ * matches Docker Desktop's qemu. */
122
+ export async function requestShutdown(channel: EmulatorChannel, timeoutMs = 10_000): Promise<void> {
123
+ await unaryCall(channel, "setVmState", { state: "SHUTDOWN" }, timeoutMs);
124
+ }