@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,397 @@
1
+ /**
2
+ * Creating, wiping and deleting AVDs.
3
+ *
4
+ * Every SDK tool is called as an **argv array**, never a shell string (plan §Phase 4: "Các API
5
+ * typed bọc SDK tools, không nối chuỗi shell"). The browser names a profile and an image out of
6
+ * lists this file produced; it never supplies a flag.
7
+ *
8
+ * Two things here exist only because the defaults are wrong for PPM:
9
+ *
10
+ * - **`hw.keyboard=yes` is written after creation, always.** `avdmanager` writes `no` (Android
11
+ * Studio writes `yes`), and with it off the gRPC `sendKey` path returns `OK` and **not one
12
+ * character reaches the guest** — no error, no log, while touch keeps working perfectly.
13
+ * Measured in Phase 0 after ruling out the allowlist and `-no-window`.
14
+ * - **A device profile is not optional.** `avdmanager create avd` with no `--device` produces a
15
+ * **320×640** AVD (measured: `hw.lcd.width=320`), which looks like a broken emulator rather
16
+ * than a missing argument.
17
+ */
18
+ import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
19
+ import { join } from "node:path";
20
+ import { avdIdFor, listAvds, type AvdSummary } from "./avd-list.ts";
21
+ import type { SystemImage } from "./system-images.ts";
22
+ import { avdHomeEnv } from "./sdk-discovery.ts";
23
+ import { AVD_LIMITS, validateAvdName } from "../../shared/android-avd.ts";
24
+ import type { AvdDeviceProfile, CreateAvdRequest } from "../../shared/android-avd.ts";
25
+
26
+ // The name rule and the bounds are shared with the browser's create form (see
27
+ // `src/shared/android-avd.ts`) and re-exported here so callers keep one import.
28
+ export { AVD_LIMITS, validateAvdName };
29
+ export type { CreateAvdRequest };
30
+ export type DeviceProfile = AvdDeviceProfile;
31
+
32
+ export interface ToolResult {
33
+ ok: boolean;
34
+ message: string;
35
+ output: string;
36
+ }
37
+
38
+ // ---------------------------------------------------------------------------------------------
39
+ // Device profiles
40
+ // ---------------------------------------------------------------------------------------------
41
+
42
+ /**
43
+ * Parse `avdmanager list device`.
44
+ *
45
+ * Shape, measured on cmdline-tools for emulator 36.5.10 (88 entries):
46
+ *
47
+ * ```
48
+ * id: 46 or "pixel_9"
49
+ * Name: Pixel 9
50
+ * OEM : Google
51
+ * ---------
52
+ * ```
53
+ *
54
+ * `Tag :` is **optional** and absent on most phone profiles including every Pixel, so a parser
55
+ * that requires it silently drops the entries people actually want.
56
+ */
57
+ export function parseDeviceProfiles(output: string): DeviceProfile[] {
58
+ const profiles: DeviceProfile[] = [];
59
+ let current: { id: string; name: string; oem: string; tag: string | null } | null = null;
60
+
61
+ const flush = () => {
62
+ if (current && current.id) profiles.push({ ...current, kind: kindOf(current.id, current.name) });
63
+ current = null;
64
+ };
65
+
66
+ for (const rawLine of output.split("\n")) {
67
+ const line = rawLine.trimEnd();
68
+ const idMatch = /^id:\s*\d+\s+or\s+"([^"]+)"\s*$/.exec(line.trim());
69
+ if (idMatch) {
70
+ flush();
71
+ current = { id: idMatch[1]!, name: idMatch[1]!, oem: "", tag: null };
72
+ continue;
73
+ }
74
+ if (!current) continue;
75
+ const name = /^\s*Name:\s*(.+)$/.exec(line);
76
+ if (name) { current.name = name[1]!.trim(); continue; }
77
+ const oem = /^\s*OEM\s*:\s*(.+)$/.exec(line);
78
+ if (oem) { current.oem = oem[1]!.trim(); continue; }
79
+ const tag = /^\s*Tag\s*:\s*(.+)$/.exec(line);
80
+ if (tag) { current.tag = tag[1]!.trim(); continue; }
81
+ if (line.startsWith("---")) flush();
82
+ }
83
+ flush();
84
+ return profiles;
85
+ }
86
+
87
+ function kindOf(id: string, name: string): DeviceProfile["kind"] {
88
+ const haystack = `${id} ${name}`.toLowerCase();
89
+ if (/\btv\b|television/.test(haystack)) return "tv";
90
+ if (/wear|watch/.test(haystack)) return "wear";
91
+ if (/automotive|\bcar\b/.test(haystack)) return "automotive";
92
+ if (/desktop|freeform|chromebook/.test(haystack)) return "desktop";
93
+ if (/tablet|\bpad\b|nexus (7|9|10)|fold/.test(haystack)) return "tablet";
94
+ if (/phone|pixel|galaxy|nexus|\bxl\b/.test(haystack)) return "phone";
95
+ return "other";
96
+ }
97
+
98
+ /**
99
+ * ~730 ms measured, so it is cached for the life of the process — the SDK's stock profile list
100
+ * does not change while PPM runs, and the create dialog would otherwise pay it on every open.
101
+ */
102
+ let profileCache: DeviceProfile[] | null = null;
103
+
104
+ export async function listDeviceProfiles(avdmanagerPath: string | null): Promise<DeviceProfile[]> {
105
+ if (profileCache) return profileCache;
106
+ if (!avdmanagerPath) return [];
107
+ const result = await runTool(avdmanagerPath, ["list", "device"], 60_000);
108
+ if (!result.ok) return [];
109
+ const parsed = parseDeviceProfiles(result.output);
110
+ if (parsed.length > 0) profileCache = parsed;
111
+ return parsed;
112
+ }
113
+
114
+ export function _resetProfileCache(): void { profileCache = null; }
115
+
116
+ // ---------------------------------------------------------------------------------------------
117
+ // Create
118
+ // ---------------------------------------------------------------------------------------------
119
+
120
+ function clamp(value: number | undefined, bounds: { min: number; max: number; default: number }): number {
121
+ if (value === undefined || !Number.isFinite(value)) return bounds.default;
122
+ return Math.min(bounds.max, Math.max(bounds.min, Math.round(value)));
123
+ }
124
+
125
+ export async function createAvd(
126
+ opts: CreateAvdRequest & { avdmanagerPath: string; avdHome: string; images: SystemImage[] },
127
+ ): Promise<ToolResult & { avd: AvdSummary | null }> {
128
+ const nameProblem = validateAvdName(opts.name);
129
+ if (nameProblem) return { ok: false, message: nameProblem, output: "", avd: null };
130
+
131
+ // The image and the profile must be ones *this* host has, named out of the lists PPM issued —
132
+ // never a string the browser made up, which is what keeps this from being an argument proxy.
133
+ const image = opts.images.find((i) => i.id === opts.systemImage);
134
+ if (!image) return { ok: false, message: "that system image is not installed on this host", output: "", avd: null };
135
+ if (!/^[A-Za-z0-9._-]+$/.test(opts.deviceProfile)) {
136
+ return { ok: false, message: "that is not a device profile id", output: "", avd: null };
137
+ }
138
+ if (listAvds(opts.avdHome).some((a) => a.name === opts.name)) {
139
+ return { ok: false, message: `an AVD called ${opts.name} already exists`, output: "", avd: null };
140
+ }
141
+
142
+ // No `--force`: overwriting an existing AVD is a destructive act wearing a create's clothes,
143
+ // and the duplicate check above is what the user should see instead.
144
+ const args = [
145
+ "create", "avd",
146
+ "--name", opts.name,
147
+ "--package", image.id,
148
+ "--device", opts.deviceProfile,
149
+ "--abi", image.abi,
150
+ ];
151
+ const sdCardMb = clamp(opts.sdCardMb, AVD_LIMITS.sdCardMb);
152
+ if (sdCardMb > 0) args.push("--sdcard", `${sdCardMb}M`);
153
+
154
+ // `ANDROID_AVD_HOME` has to name a directory that **exists**. Pointed at a missing one,
155
+ // `avdmanager` does not fail — it falls back to `~/.android/avd` and writes the AVD into the
156
+ // user's own directory, which is the same silent outcome as not setting the variable at all
157
+ // (measured both ways). One `mkdir` is the whole fix; the config check below is what catches
158
+ // it if some other cause puts the AVD somewhere else again.
159
+ mkdirSync(opts.avdHome, { recursive: true });
160
+
161
+ // `avdmanager` asks "Do you wish to create a custom hardware profile?" on stdin and blocks
162
+ // forever without an answer. `no` is the answer, and it is given rather than left to a tty.
163
+ const result = await runTool(opts.avdmanagerPath, args, 180_000, "no\n", avdHomeEnv(opts.avdHome));
164
+ if (!result.ok) return { ...result, avd: null };
165
+
166
+ const dir = join(opts.avdHome, `${opts.name}.avd`);
167
+ if (!existsSync(join(dir, "config.ini"))) {
168
+ return {
169
+ ok: false,
170
+ message: `avdmanager exited cleanly but wrote no config under ${opts.avdHome}`,
171
+ output: result.output,
172
+ avd: null,
173
+ };
174
+ }
175
+
176
+ applyPpmDefaults(join(dir, "config.ini"), {
177
+ ramMb: clamp(opts.ramMb, AVD_LIMITS.ramMb),
178
+ storageMb: clamp(opts.storageMb, AVD_LIMITS.storageMb),
179
+ });
180
+
181
+ const avd = listAvds(opts.avdHome).find((a) => a.avdId === avdIdFor(dir)) ?? null;
182
+ return { ok: true, message: `created ${opts.name}`, output: result.output, avd };
183
+ }
184
+
185
+ /**
186
+ * The settings PPM needs that `avdmanager` does not write.
187
+ *
188
+ * Rewrites only the keys named here and leaves every other line byte-identical, so a config this
189
+ * code does not understand survives untouched.
190
+ */
191
+ export function applyPpmDefaults(configPath: string, opts: { ramMb: number; storageMb: number }): void {
192
+ const wanted: Record<string, string> = {
193
+ // The whole reason this function exists — see the file header.
194
+ "hw.keyboard": "yes",
195
+ "hw.ramSize": `${opts.ramMb}M`,
196
+ "disk.dataPartition.size": `${opts.storageMb}M`,
197
+ // Touch, not a d-pad: the viewer sends pointer events.
198
+ "hw.screen": "multi-touch",
199
+ };
200
+ writeConfigKeys(configPath, wanted);
201
+ }
202
+
203
+ /** Read-modify-write of a `key=value` file, preserving order and unknown keys. */
204
+ export function writeConfigKeys(configPath: string, values: Record<string, string>): void {
205
+ let lines: string[];
206
+ try { lines = readFileSync(configPath, "utf8").split("\n"); } catch { return; }
207
+
208
+ const remaining = new Map(Object.entries(values));
209
+ const out = lines.map((line) => {
210
+ const eq = line.indexOf("=");
211
+ if (eq <= 0) return line;
212
+ const key = line.slice(0, eq).trim();
213
+ if (!remaining.has(key)) return line;
214
+ const value = remaining.get(key)!;
215
+ remaining.delete(key);
216
+ return `${key}=${value}`;
217
+ });
218
+ // A file that ends in a newline splits to a trailing empty string; appending after it opens a
219
+ // blank line in the middle of the config rather than at its end.
220
+ while (out.length > 0 && out[out.length - 1]!.trim() === "") out.pop();
221
+ for (const [key, value] of remaining) out.push(`${key}=${value}`);
222
+
223
+ // Keep the file newline-terminated; emulator's own parser is fine either way but a diff is not.
224
+ const text = out.join("\n").replace(/\n*$/, "\n");
225
+ writeFileSync(configPath, text);
226
+ }
227
+
228
+ // ---------------------------------------------------------------------------------------------
229
+ // Wipe
230
+ // ---------------------------------------------------------------------------------------------
231
+
232
+ /**
233
+ * What a wipe removes, by name.
234
+ *
235
+ * An **allowlist**, not a glob: a file this code has never heard of is *kept*, because the
236
+ * alternative is a future emulator version quietly losing something with no way to notice. Also
237
+ * never `config.ini` or the sibling `<name>.ini` — those are the AVD's definition, and removing
238
+ * them is a delete wearing a wipe's clothes.
239
+ *
240
+ * `emulator -avd <n> -wipe-data` is the documented route and does the same thing, but it *boots*
241
+ * the AVD to do it, which contradicts the plan's "chỉ khi stopped".
242
+ *
243
+ * **`userdata.img` is deliberately not in this list.** `emulator -help-disk-images` groups it
244
+ * with `system.img` under the *initial* images and spells out what a wipe is: "Copy the content
245
+ * of the *initial* user data image (userdata.img) into the writable one (userdata-qemu.img)".
246
+ * Deleting it is deleting the factory copy a wipe restores from. A current AVD keeps it in the
247
+ * system-image directory instead — measured: a fresh AVD here has none, and boots fine after a
248
+ * wipe that removed it — so this only bites an AVD made by older tooling, silently, on its next
249
+ * boot. Exactly what the allowlist exists to prevent.
250
+ */
251
+ const WIPE_FILES = [
252
+ "userdata-qemu.img", "userdata-qemu.img.qcow2",
253
+ "cache.img", "cache.img.qcow2",
254
+ "sdcard.img", "sdcard.img.qcow2",
255
+ "encryptionkey.img.qcow2",
256
+ "version_num.cache", "bootcompleted.ini", "read-snapshot.txt",
257
+ "hardware-qemu.ini", "emu-launch-params.txt", "multiinstance.lock",
258
+ ];
259
+ const WIPE_DIRS = ["snapshots", "tmpAdbCmds"];
260
+
261
+ export interface WipeOutcome {
262
+ ok: boolean;
263
+ message: string;
264
+ /** What was actually removed, so the report is the truth rather than the intent. */
265
+ removed: string[];
266
+ freedBytes: number;
267
+ }
268
+
269
+ export function wipeAvdData(avdDir: string): WipeOutcome {
270
+ if (!existsSync(join(avdDir, "config.ini"))) {
271
+ return { ok: false, message: "that is not an AVD directory", removed: [], freedBytes: 0 };
272
+ }
273
+ const removed: string[] = [];
274
+ let freed = 0;
275
+
276
+ for (const name of WIPE_FILES) {
277
+ const path = join(avdDir, name);
278
+ if (!existsSync(path)) continue;
279
+ try {
280
+ freed += Bun.file(path).size;
281
+ rmSync(path, { force: true });
282
+ removed.push(name);
283
+ } catch { /* locked by something; the caller checks the AVD is stopped first */ }
284
+ }
285
+ for (const name of WIPE_DIRS) {
286
+ const path = join(avdDir, name);
287
+ if (!existsSync(path)) continue;
288
+ try {
289
+ freed += dirBytes(path);
290
+ rmSync(path, { recursive: true, force: true });
291
+ removed.push(`${name}/`);
292
+ } catch { /* same */ }
293
+ }
294
+
295
+ return {
296
+ ok: true,
297
+ message: removed.length > 0 ? `wiped ${removed.length} item(s)` : "there was nothing to wipe",
298
+ removed,
299
+ freedBytes: freed,
300
+ };
301
+ }
302
+
303
+ function dirBytes(dir: string): number {
304
+ let total = 0;
305
+ try {
306
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
307
+ const full = join(dir, entry.name);
308
+ if (entry.isDirectory()) total += dirBytes(full);
309
+ else { try { total += Bun.file(full).size; } catch { /* gone */ } }
310
+ }
311
+ } catch { /* gone */ }
312
+ return total;
313
+ }
314
+
315
+ // ---------------------------------------------------------------------------------------------
316
+ // Delete
317
+ // ---------------------------------------------------------------------------------------------
318
+
319
+ export async function deleteAvd(avdmanagerPath: string, name: string, avdHome: string): Promise<ToolResult> {
320
+ const problem = validateAvdName(name);
321
+ if (problem) return { ok: false, message: problem, output: "" };
322
+ // Without the home, `delete` looks in `~/.android/avd` and answers "there is no AVD named X"
323
+ // for one that plainly exists in the listing — the same trap as create, read backwards.
324
+ return runTool(avdmanagerPath, ["delete", "avd", "--name", name], 60_000, "", avdHomeEnv(avdHome));
325
+ }
326
+
327
+ // ---------------------------------------------------------------------------------------------
328
+
329
+ /**
330
+ * One SDK tool run, argv only.
331
+ *
332
+ * `avdmanager` writes its progress bar and most of its diagnostics to **stderr** while still
333
+ * exiting 0, so success cannot be read off stderr being empty; and it prompts on stdin, so
334
+ * `stdin` is always fed rather than inherited — a prompt with no tty is a hang, not an error.
335
+ */
336
+ async function runTool(
337
+ path: string,
338
+ args: string[],
339
+ timeoutMs: number,
340
+ stdin = "",
341
+ env: NodeJS.ProcessEnv = process.env,
342
+ ): Promise<ToolResult> {
343
+ let proc: ReturnType<typeof spawnTool>;
344
+ try {
345
+ proc = spawnTool(path, args, stdin, env);
346
+ } catch (e) {
347
+ // Bun.spawn throws synchronously on a missing binary (CLAUDE.md, host-info/spawn-runner.ts).
348
+ return { ok: false, message: `could not run ${path}: ${(e as Error).message}`, output: "" };
349
+ }
350
+
351
+ const collected = (async () => {
352
+ const [out, errOut] = await Promise.all([
353
+ new Response(proc.stdout).text(),
354
+ new Response(proc.stderr).text(),
355
+ ]);
356
+ return { text: `${out}${errOut}`, exitCode: await proc.exited };
357
+ })();
358
+ collected.catch(() => { /* abandoned on timeout */ });
359
+
360
+ let timer: ReturnType<typeof setTimeout> | undefined;
361
+ const expired = new Promise<"expired">((resolve) => {
362
+ timer = setTimeout(() => { try { proc.kill(); } catch { /* gone */ } resolve("expired"); }, timeoutMs);
363
+ });
364
+
365
+ try {
366
+ const outcome = await Promise.race([collected, expired]);
367
+ if (outcome === "expired") {
368
+ return { ok: false, message: `${basenameOf(path)} did not finish in ${Math.round(timeoutMs / 1000)}s`, output: "" };
369
+ }
370
+ // The progress bar is carriage-return animation; keeping it makes every error message a wall.
371
+ // CRLF goes first: the SDK's Java tools end every line with it on Windows, and the strip
372
+ // would otherwise erase each of those lines as if it were a frame of the bar.
373
+ const output = outcome.text.replace(/\r\n/g, "\n").replace(/^.*\r/gm, "").trim();
374
+ if (outcome.exitCode !== 0) {
375
+ const firstError = output.split("\n").map((l) => l.trim()).find((l) => /^error/i.test(l));
376
+ return { ok: false, message: firstError ?? output.split("\n")[0] ?? `exited with ${outcome.exitCode}`, output };
377
+ }
378
+ return { ok: true, message: "done", output };
379
+ } finally {
380
+ clearTimeout(timer);
381
+ }
382
+ }
383
+
384
+ /** Named so `stdout` keeps its pipe type — `ReturnType<typeof Bun.spawn>` widens it (CLAUDE.md). */
385
+ function spawnTool(path: string, args: string[], stdin: string, env: NodeJS.ProcessEnv) {
386
+ return Bun.spawn([path, ...args], {
387
+ stdin: new TextEncoder().encode(stdin),
388
+ stdout: "pipe",
389
+ stderr: "pipe",
390
+ env,
391
+ windowsHide: true,
392
+ });
393
+ }
394
+
395
+ function basenameOf(path: string): string {
396
+ return path.split(/[\\/]/).pop() ?? path;
397
+ }
@@ -0,0 +1,223 @@
1
+ /**
2
+ * What devices exist, which of them PPM started, and how to tell the two apart across a restart.
3
+ *
4
+ * Identity, per plan §5:
5
+ * - `avdId` is stable and derived from the AVD's canonical directory (see `avd-list.ts`). It
6
+ * survives restarts and is what a tab is pinned to.
7
+ * - `deviceId` names one *runtime instance*. It changes when the emulator restarts, and carries
8
+ * a `generation` so a late message from a previous run can be rejected rather than applied.
9
+ * - The ADB serial is deliberately **not** an identity: `emulator-5554` is reused by whatever
10
+ * boots next.
11
+ *
12
+ * Ownership decides who may stop a device. PPM may stop only what PPM started; an emulator the
13
+ * user launched from Android Studio is offered Disconnect and nothing more (plan ADR-D). That
14
+ * has to survive a PPM restart, so a minimal record is persisted — pid plus a *start identity*,
15
+ * because pids are reused and a bare pid would eventually name someone else's process.
16
+ */
17
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
18
+ import { join } from "node:path";
19
+ import { platform } from "node:os";
20
+ import { getPpmDir } from "../ppm-dir.ts";
21
+ import { listAvds, type AvdSummary } from "./avd-list.ts";
22
+ import { findRunningEmulators, type RunningEmulator } from "./emulator-discovery.ts";
23
+
24
+ export type DeviceState =
25
+ | "stopped" // no process
26
+ | "starting" // process spawned, no gRPC yet
27
+ | "booting" // gRPC answers, guest not booted
28
+ | "ready" // guest reports boot complete
29
+ | "stopping"
30
+ | "error";
31
+
32
+ export interface RuntimeDevice {
33
+ deviceId: string;
34
+ generation: number;
35
+ pid: number;
36
+ grpcPort: number;
37
+ adbSerial: string | null;
38
+ /** True when this PPM instance started it and may therefore stop it. */
39
+ ownedByPpm: boolean;
40
+ }
41
+
42
+ export interface DeviceEntry {
43
+ avdId: string;
44
+ name: string;
45
+ apiLevel: number | null;
46
+ abi: string | null;
47
+ deviceProfile: string | null;
48
+ displayWidth: number | null;
49
+ displayHeight: number | null;
50
+ hardwareKeyboard: boolean;
51
+ lockedByAnotherProcess: boolean;
52
+ state: DeviceState;
53
+ runtime: RuntimeDevice | null;
54
+ }
55
+
56
+ interface OwnershipRecord {
57
+ avdName: string;
58
+ pid: number;
59
+ /** Something that, with the pid, identifies *this* process run. Linux: /proc stat starttime. */
60
+ startIdentity: string | null;
61
+ generation: number;
62
+ startedAt: number;
63
+ }
64
+
65
+ const OWNERSHIP_FILE = () => join(getPpmDir(), "android-ownership.json");
66
+
67
+ /**
68
+ * A token that distinguishes this process run from a future one that reuses the pid.
69
+ *
70
+ * Linux exposes the process start time in /proc/<pid>/stat field 22 (jiffies since boot), which
71
+ * is exactly the disambiguator pids lack. Elsewhere there is no cheap equivalent, so we return
72
+ * null and fall back to a liveness check plus the discovery file's own AVD name — weaker, and
73
+ * that weakness is why `stop` re-verifies through the discovery file before acting.
74
+ */
75
+ export function processStartIdentity(pid: number): string | null {
76
+ if (platform() !== "linux") return null;
77
+ try {
78
+ const stat = readFileSync(`/proc/${pid}/stat`, "utf8");
79
+ // The comm field is parenthesised and may itself contain spaces, so split after it.
80
+ const afterComm = stat.slice(stat.lastIndexOf(")") + 2).split(" ");
81
+ // Fields from here are 3..N; starttime is field 22, i.e. index 19 in this slice.
82
+ return afterComm[19] ?? null;
83
+ } catch {
84
+ return null;
85
+ }
86
+ }
87
+
88
+ function readOwnership(): OwnershipRecord[] {
89
+ try {
90
+ const raw = JSON.parse(readFileSync(OWNERSHIP_FILE(), "utf8"));
91
+ return Array.isArray(raw) ? (raw as OwnershipRecord[]) : [];
92
+ } catch {
93
+ return [];
94
+ }
95
+ }
96
+
97
+ function writeOwnership(records: OwnershipRecord[]): void {
98
+ try {
99
+ mkdirSync(getPpmDir(), { recursive: true });
100
+ writeFileSync(OWNERSHIP_FILE(), JSON.stringify(records, null, 2));
101
+ } catch (e) {
102
+ // Losing the record costs stop rights after a restart, not correctness: an unowned device is
103
+ // offered Disconnect. Never fail a start over it.
104
+ console.warn(`[android] could not persist ownership: ${(e as Error).message}`);
105
+ }
106
+ }
107
+
108
+ /** Record that this PPM started `avdName` as `pid`. Returns the generation for that run. */
109
+ export function claimOwnership(avdName: string, pid: number): number {
110
+ const records = readOwnership().filter((r) => r.avdName !== avdName);
111
+ const previous = readOwnership().find((r) => r.avdName === avdName);
112
+ const generation = (previous?.generation ?? 0) + 1;
113
+ records.push({
114
+ avdName,
115
+ pid,
116
+ startIdentity: processStartIdentity(pid),
117
+ generation,
118
+ startedAt: Date.now(),
119
+ });
120
+ writeOwnership(records);
121
+ return generation;
122
+ }
123
+
124
+ export function releaseOwnership(avdName: string): void {
125
+ writeOwnership(readOwnership().filter((r) => r.avdName !== avdName));
126
+ }
127
+
128
+ /**
129
+ * Does the ownership record still describe this live emulator?
130
+ *
131
+ * Both the pid and the start identity must match. A record whose pid is alive but whose start
132
+ * identity differs is a *reused pid* — the emulator PPM started is gone and something unrelated
133
+ * now holds that number, so PPM must not claim the right to stop it.
134
+ */
135
+ function ownershipMatches(record: OwnershipRecord | undefined, emulator: RunningEmulator): boolean {
136
+ if (!record) return false;
137
+ if (record.pid !== emulator.pid) return false;
138
+ const live = processStartIdentity(emulator.pid);
139
+ if (record.startIdentity !== null && live !== null) return record.startIdentity === live;
140
+ // No start identity available on this OS: fall back to the AVD name agreeing, which the
141
+ // discovery file has already been verified against.
142
+ return record.avdName === emulator.avdName;
143
+ }
144
+
145
+ export function generationFor(avdName: string): number {
146
+ return readOwnership().find((r) => r.avdName === avdName)?.generation ?? 0;
147
+ }
148
+
149
+ /** Merge the AVDs on disk with whatever is running, and attach ownership. */
150
+ export function listDevices(avdHome: string): DeviceEntry[] {
151
+ const avds = listAvds(avdHome);
152
+ const running = findRunningEmulators();
153
+ const ownership = readOwnership();
154
+
155
+ const byName = new Map<string, RunningEmulator>();
156
+ for (const e of running) byName.set(e.avdName, e);
157
+
158
+ const entries: DeviceEntry[] = avds.map((avd) => toEntry(avd, byName.get(avd.name), ownership));
159
+
160
+ // An emulator running from an AVD home PPM is not listing still deserves a row: the user can
161
+ // see and disconnect from it, they just cannot start or stop it from here.
162
+ for (const e of running) {
163
+ if (entries.some((x) => x.name === e.avdName)) continue;
164
+ entries.push({
165
+ avdId: `external:${e.avdName}`,
166
+ name: e.avdName,
167
+ apiLevel: null, abi: null, deviceProfile: null,
168
+ displayWidth: null, displayHeight: null,
169
+ hardwareKeyboard: true, // unknown; assume on so no misleading warning is shown
170
+ lockedByAnotherProcess: false,
171
+ state: "ready",
172
+ runtime: runtimeOf(e, ownership),
173
+ });
174
+ }
175
+ return entries;
176
+ }
177
+
178
+ function runtimeOf(e: RunningEmulator, ownership: OwnershipRecord[]): RuntimeDevice {
179
+ const record = ownership.find((r) => r.avdName === e.avdName);
180
+ const owned = ownershipMatches(record, e);
181
+ return {
182
+ // Runtime identity, not stable: pid + port is unique while the process lives, and changes
183
+ // on every restart, which is exactly what `deviceId` is supposed to express.
184
+ deviceId: `${e.pid}:${e.grpcPort}`,
185
+ generation: owned ? record!.generation : 0,
186
+ pid: e.pid,
187
+ grpcPort: e.grpcPort,
188
+ adbSerial: e.adbSerial,
189
+ ownedByPpm: owned,
190
+ };
191
+ }
192
+
193
+ function toEntry(avd: AvdSummary, e: RunningEmulator | undefined, ownership: OwnershipRecord[]): DeviceEntry {
194
+ return {
195
+ avdId: avd.avdId,
196
+ name: avd.name,
197
+ apiLevel: avd.apiLevel,
198
+ abi: avd.abi,
199
+ deviceProfile: avd.deviceProfile,
200
+ displayWidth: avd.displayWidth,
201
+ displayHeight: avd.displayHeight,
202
+ hardwareKeyboard: avd.hardwareKeyboard,
203
+ lockedByAnotherProcess: avd.lockedByAnotherProcess && !e,
204
+ state: e ? "ready" : "stopped",
205
+ runtime: e ? runtimeOf(e, ownership) : null,
206
+ };
207
+ }
208
+
209
+ export function findDeviceByAvdId(avdHome: string, avdId: string): DeviceEntry | null {
210
+ return listDevices(avdHome).find((d) => d.avdId === avdId) ?? null;
211
+ }
212
+
213
+ /**
214
+ * Resolve a runtime device id back to the live emulator it names.
215
+ *
216
+ * Goes to discovery rather than to `listDevices`, because the WS session needs no AVD home and
217
+ * must not depend on one being resolvable: the id is `pid:grpcPort`, and the pid being alive with
218
+ * that port is the whole claim. An emulator that has restarted has a new id, so a stale id simply
219
+ * finds nothing — which is the behaviour a client holding an old session wants.
220
+ */
221
+ export function findRunningByDeviceId(deviceId: string): RunningEmulator | null {
222
+ return findRunningEmulators().find((e) => `${e.pid}:${e.grpcPort}` === deviceId) ?? null;
223
+ }