@hienlh/ppm 0.23.21 → 0.23.23

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 (497) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/CLAUDE.md +6 -3
  3. package/README.md +1 -1
  4. package/assets/skills/ppm/SKILL.md +1 -1
  5. package/assets/skills/ppm/references/cli-reference.md +4 -4
  6. package/assets/skills/ppm/references/http-api.md +2 -1
  7. package/dist/web/assets/{accounts-settings-section-DD88DRdx.js → accounts-settings-section-Cg-LncY6.js} +1 -1
  8. package/dist/web/assets/accounts-settings-section-Cg-LncY6.js.br +0 -0
  9. package/dist/web/assets/accounts-settings-section-Cg-LncY6.js.gz +0 -0
  10. package/dist/web/assets/{agent-session-sheet-C0PhhLKR.js → agent-session-sheet-DMdV8GOu.js} +2 -2
  11. package/dist/web/assets/agent-session-sheet-DMdV8GOu.js.br +0 -0
  12. package/dist/web/assets/agent-session-sheet-DMdV8GOu.js.gz +0 -0
  13. package/dist/web/assets/{agent-session-window-content-D0XZgh4U.js → agent-session-window-content-CYg58J-V.js} +1 -1
  14. package/dist/web/assets/agent-session-window-content-CYg58J-V.js.br +0 -0
  15. package/dist/web/assets/agent-session-window-content-CYg58J-V.js.gz +0 -0
  16. package/dist/web/assets/{ai-resource-editor-CpfQB6A7.js → ai-resource-editor-BvEnn0Z_.js} +1 -1
  17. package/dist/web/assets/ai-resource-editor-BvEnn0Z_.js.br +0 -0
  18. package/dist/web/assets/ai-resource-editor-BvEnn0Z_.js.gz +0 -0
  19. package/dist/web/assets/{ai-settings-section-DqE67XNC.js → ai-settings-section-Bo-nf2rY.js} +1 -1
  20. package/dist/web/assets/ai-settings-section-DAqiu_11.js +1 -0
  21. package/dist/web/assets/ai-settings-section-DAqiu_11.js.br +0 -0
  22. package/dist/web/assets/ai-settings-section-DAqiu_11.js.gz +0 -0
  23. package/dist/web/assets/{android-tab-4Z4AgHz1.js → android-tab-C-wmlFc0.js} +1 -1
  24. package/dist/web/assets/android-tab-C-wmlFc0.js.br +0 -0
  25. package/dist/web/assets/android-tab-C-wmlFc0.js.gz +0 -0
  26. package/dist/web/assets/{appearance-settings-section-DjQjiJF7.js → appearance-settings-section-CYqX6BGh.js} +1 -1
  27. package/dist/web/assets/appearance-settings-section-CYqX6BGh.js.br +0 -0
  28. package/dist/web/assets/appearance-settings-section-CYqX6BGh.js.gz +0 -0
  29. package/dist/web/assets/{apps-panel-_lxY9xbc.js → apps-panel-D1VTBxgC.js} +1 -1
  30. package/dist/web/assets/apps-panel-D1VTBxgC.js.br +0 -0
  31. package/dist/web/assets/apps-panel-D1VTBxgC.js.gz +0 -0
  32. package/dist/web/assets/{branch-review-tab-BzrhnFSs.js → branch-review-tab-BG6vxSsC.js} +1 -1
  33. package/dist/web/assets/branch-review-tab-BG6vxSsC.js.br +0 -0
  34. package/dist/web/assets/branch-review-tab-BG6vxSsC.js.gz +0 -0
  35. package/dist/web/assets/chat-tab-BAwGLGyR.js +19 -0
  36. package/dist/web/assets/chat-tab-BAwGLGyR.js.br +0 -0
  37. package/dist/web/assets/chat-tab-BAwGLGyR.js.gz +0 -0
  38. package/dist/web/assets/{chat-tab-BKHepkYi.js → chat-tab-BJ0Mi2sd.js} +1 -1
  39. package/dist/web/assets/chat-tab-BJ0Mi2sd.js.br +0 -0
  40. package/dist/web/assets/chat-tab-BJ0Mi2sd.js.gz +0 -0
  41. package/dist/web/assets/{code-editor-COWmNXO4.js → code-editor-DuwbwTpM.js} +4 -4
  42. package/dist/web/assets/code-editor-DuwbwTpM.js.br +0 -0
  43. package/dist/web/assets/code-editor-DuwbwTpM.js.gz +0 -0
  44. package/dist/web/assets/{codex-reset-credit-button-BHt4yJet.js → codex-reset-credit-button-tIwfnf2G.js} +1 -1
  45. package/dist/web/assets/codex-reset-credit-button-tIwfnf2G.js.br +0 -0
  46. package/dist/web/assets/codex-reset-credit-button-tIwfnf2G.js.gz +0 -0
  47. package/dist/web/assets/{conflict-editor-tj-1gzKr.js → conflict-editor-D2n-hQqs.js} +1 -1
  48. package/dist/web/assets/conflict-editor-D2n-hQqs.js.br +0 -0
  49. package/dist/web/assets/conflict-editor-D2n-hQqs.js.gz +0 -0
  50. package/dist/web/assets/{connection-form-tab-BV2QX8Cf.js → connection-form-tab-DYqPNq_-.js} +1 -1
  51. package/dist/web/assets/connection-form-tab-DYqPNq_-.js.br +0 -0
  52. package/dist/web/assets/connection-form-tab-DYqPNq_-.js.gz +0 -0
  53. package/dist/web/assets/{csv-preview-xhE2esgz.js → csv-preview-Xh3Z6BS5.js} +1 -1
  54. package/dist/web/assets/csv-preview-Xh3Z6BS5.js.br +0 -0
  55. package/dist/web/assets/csv-preview-Xh3Z6BS5.js.gz +0 -0
  56. package/dist/web/assets/{db-tab-parts-C3MsjOID.js → db-tab-parts-Dqnb3GCP.js} +1 -1
  57. package/dist/web/assets/db-tab-parts-Dqnb3GCP.js.br +0 -0
  58. package/dist/web/assets/db-tab-parts-Dqnb3GCP.js.gz +0 -0
  59. package/dist/web/assets/{design-settings-section-C3LxYhIw.js → design-settings-section-DNNWgxF9.js} +1 -1
  60. package/dist/web/assets/design-settings-section-DNNWgxF9.js.br +0 -0
  61. package/dist/web/assets/design-settings-section-DNNWgxF9.js.gz +0 -0
  62. package/dist/web/assets/{design-tab-DYcvjdfb.js → design-tab-BoAdSpZZ.js} +1 -1
  63. package/dist/web/assets/design-tab-BoAdSpZZ.js.br +0 -0
  64. package/dist/web/assets/design-tab-BoAdSpZZ.js.gz +0 -0
  65. package/dist/web/assets/{designs-sidebar-panel-Dpr4PPFT.js → designs-sidebar-panel-BKw7HFAz.js} +1 -1
  66. package/dist/web/assets/designs-sidebar-panel-BKw7HFAz.js.br +0 -0
  67. package/dist/web/assets/designs-sidebar-panel-BKw7HFAz.js.gz +0 -0
  68. package/dist/web/assets/{diff-viewer-Fl_4jqr6.js → diff-viewer-CRmfCAZX.js} +1 -1
  69. package/dist/web/assets/diff-viewer-CRmfCAZX.js.br +0 -0
  70. package/dist/web/assets/diff-viewer-CRmfCAZX.js.gz +0 -0
  71. package/dist/web/assets/{docx-preview-bVE9shPX.js → docx-preview-zIrtL09u.js} +1 -1
  72. package/dist/web/assets/docx-preview-zIrtL09u.js.br +0 -0
  73. package/dist/web/assets/docx-preview-zIrtL09u.js.gz +0 -0
  74. package/dist/web/assets/{editor-dialog-eEOYWdbr.js → editor-dialog-jMYvXVla.js} +1 -1
  75. package/dist/web/assets/editor-dialog-jMYvXVla.js.br +0 -0
  76. package/dist/web/assets/editor-dialog-jMYvXVla.js.gz +0 -0
  77. package/dist/web/assets/{editor-language-service-D1_Ggq4s.js → editor-language-service-Yb-61Bro.js} +1 -1
  78. package/dist/web/assets/editor-language-service-Yb-61Bro.js.br +0 -0
  79. package/dist/web/assets/editor-language-service-Yb-61Bro.js.gz +0 -0
  80. package/dist/web/assets/{explorer-body-C0UOcBPJ.js → explorer-body-C6qKehmd.js} +1 -1
  81. package/dist/web/assets/explorer-body-C6qKehmd.js.br +0 -0
  82. package/dist/web/assets/explorer-body-C6qKehmd.js.gz +0 -0
  83. package/dist/web/assets/{explorer-window-content-Dxee7SuJ.js → explorer-window-content-B9P8grrY.js} +1 -1
  84. package/dist/web/assets/explorer-window-content-B9P8grrY.js.br +0 -0
  85. package/dist/web/assets/explorer-window-content-B9P8grrY.js.gz +0 -0
  86. package/dist/web/assets/{extension-manager-section-D8krnoj8.js → extension-manager-section-C45v1QtG.js} +1 -1
  87. package/dist/web/assets/extension-manager-section-C45v1QtG.js.br +0 -0
  88. package/dist/web/assets/extension-manager-section-C45v1QtG.js.gz +0 -0
  89. package/dist/web/assets/{extension-webview-CtIdV69T.js → extension-webview-DIxM-xkH.js} +1 -1
  90. package/dist/web/assets/extension-webview-DIxM-xkH.js.br +0 -0
  91. package/dist/web/assets/extension-webview-DIxM-xkH.js.gz +0 -0
  92. package/dist/web/assets/{files-settings-section-C8vtarR8.js → files-settings-section-DeVlzKXd.js} +1 -1
  93. package/dist/web/assets/files-settings-section-DeVlzKXd.js.br +0 -0
  94. package/dist/web/assets/files-settings-section-DeVlzKXd.js.gz +0 -0
  95. package/dist/web/assets/format-date-CkyHzZLA.js +1 -0
  96. package/dist/web/assets/{general-settings-section-Chu8Ut8h.js → general-settings-section-CLVDIEym.js} +1 -1
  97. package/dist/web/assets/general-settings-section-CLVDIEym.js.br +0 -0
  98. package/dist/web/assets/general-settings-section-CLVDIEym.js.gz +0 -0
  99. package/dist/web/assets/{git-log-panel-DG09p6k5.js → git-log-panel-D3Q9NRll.js} +1 -1
  100. package/dist/web/assets/git-log-panel-D3Q9NRll.js.br +0 -0
  101. package/dist/web/assets/git-log-panel-D3Q9NRll.js.gz +0 -0
  102. package/dist/web/assets/{git-review-tab-CSy8DjSC.js → git-review-tab-ORgiYbva.js} +1 -1
  103. package/dist/web/assets/git-review-tab-ORgiYbva.js.br +0 -0
  104. package/dist/web/assets/git-review-tab-ORgiYbva.js.gz +0 -0
  105. package/dist/web/assets/{glide-data-grid-BN1y4BT0.js → glide-data-grid-CtWl1BTk.js} +1 -1
  106. package/dist/web/assets/glide-data-grid-CtWl1BTk.js.br +0 -0
  107. package/dist/web/assets/glide-data-grid-CtWl1BTk.js.gz +0 -0
  108. package/dist/web/assets/{grid-save-dialog-CfSuWw2t.js → grid-save-dialog-eV25Mqpg.js} +1 -1
  109. package/dist/web/assets/grid-save-dialog-eV25Mqpg.js.br +0 -0
  110. package/dist/web/assets/grid-save-dialog-eV25Mqpg.js.gz +0 -0
  111. package/dist/web/assets/{group-chat-tab-B04QMp7A.js → group-chat-tab-CYmbYXN2.js} +1 -1
  112. package/dist/web/assets/group-chat-tab-CYmbYXN2.js.br +0 -0
  113. package/dist/web/assets/group-chat-tab-CYmbYXN2.js.gz +0 -0
  114. package/dist/web/assets/{impexp-tab-BRNouquR.js → impexp-tab-D2OKxpFq.js} +1 -1
  115. package/dist/web/assets/impexp-tab-D2OKxpFq.js.br +0 -0
  116. package/dist/web/assets/impexp-tab-D2OKxpFq.js.gz +0 -0
  117. package/dist/web/assets/{index-BpgSIM5B.js → index-D8QDpdgs.js} +7 -7
  118. package/dist/web/assets/index-D8QDpdgs.js.br +0 -0
  119. package/dist/web/assets/index-D8QDpdgs.js.gz +0 -0
  120. package/dist/web/assets/{jira-watcher-section-CJeS5r6D.js → jira-watcher-section-B3OaMlim.js} +1 -1
  121. package/dist/web/assets/{keybindings-store-Dvxq-vZD.js → keybindings-store-C_aRhBu4.js} +1 -1
  122. package/dist/web/assets/{keyboard-shortcuts-section-BKDyZHhK.js → keyboard-shortcuts-section-DXRyKrSy.js} +1 -1
  123. package/dist/web/assets/keyboard-shortcuts-section-DXRyKrSy.js.br +0 -0
  124. package/dist/web/assets/keyboard-shortcuts-section-DXRyKrSy.js.gz +0 -0
  125. package/dist/web/assets/{language-servers-section-CbbkezhA.js → language-servers-section-BTs9iuZc.js} +2 -2
  126. package/dist/web/assets/language-servers-section-BTs9iuZc.js.br +0 -0
  127. package/dist/web/assets/language-servers-section-BTs9iuZc.js.gz +0 -0
  128. package/dist/web/assets/{logs-app-Blr1-v4q.js → logs-app-BlhVTOYN.js} +1 -1
  129. package/dist/web/assets/logs-app-BlhVTOYN.js.br +0 -0
  130. package/dist/web/assets/logs-app-BlhVTOYN.js.gz +0 -0
  131. package/dist/web/assets/{logs-tab-CGPtn2tU.js → logs-tab-kqNARbz0.js} +1 -1
  132. package/dist/web/assets/{logs-window-content-DAczcNmh.js → logs-window-content-UUCJJCqG.js} +1 -1
  133. package/dist/web/assets/{lsp-client-Ds3U2O52.js → lsp-client-BTlK_8lb.js} +1 -1
  134. package/dist/web/assets/lsp-client-BTlK_8lb.js.br +0 -0
  135. package/dist/web/assets/lsp-client-BTlK_8lb.js.gz +0 -0
  136. package/dist/web/assets/{markdown-renderer-DbJVwfUH.js → markdown-renderer-ClGxDien.js} +1 -1
  137. package/dist/web/assets/{markdown-renderer-DzCPadpB.js → markdown-renderer-DpsToFFB.js} +1 -1
  138. package/dist/web/assets/markdown-renderer-DpsToFFB.js.br +0 -0
  139. package/dist/web/assets/markdown-renderer-DpsToFFB.js.gz +0 -0
  140. package/dist/web/assets/{mobile-explorer-sheet-BClCMXb8.js → mobile-explorer-sheet-CybJbScN.js} +1 -1
  141. package/dist/web/assets/mobile-explorer-sheet-CybJbScN.js.br +0 -0
  142. package/dist/web/assets/mobile-explorer-sheet-CybJbScN.js.gz +0 -0
  143. package/dist/web/assets/{new-design-dialog-HRP9H11v.js → new-design-dialog-9WTgcJoj.js} +1 -1
  144. package/dist/web/assets/new-design-dialog-9WTgcJoj.js.br +0 -0
  145. package/dist/web/assets/new-design-dialog-9WTgcJoj.js.gz +0 -0
  146. package/dist/web/assets/{notification-store-BomzYNQj.js → notification-store-CPVq5ec5.js} +1 -1
  147. package/dist/web/assets/notifications-settings-section-xrYII-pL.js +1 -0
  148. package/dist/web/assets/notifications-settings-section-xrYII-pL.js.br +0 -0
  149. package/dist/web/assets/notifications-settings-section-xrYII-pL.js.gz +0 -0
  150. package/dist/web/assets/{ppmbot-settings-section-BRowIIqc.js → ppmbot-settings-section-Bc46iDP9.js} +1 -1
  151. package/dist/web/assets/ppmbot-settings-section-Bc46iDP9.js.br +0 -0
  152. package/dist/web/assets/ppmbot-settings-section-Bc46iDP9.js.gz +0 -0
  153. package/dist/web/assets/{problems-panel-DdB10Ybd.js → problems-panel-D49-Xkzd.js} +1 -1
  154. package/dist/web/assets/problems-panel-D49-Xkzd.js.br +0 -0
  155. package/dist/web/assets/problems-panel-D49-Xkzd.js.gz +0 -0
  156. package/dist/web/assets/{process-table-CzLaWPe0.js → process-table-NxYTZ0n9.js} +1 -1
  157. package/dist/web/assets/process-table-NxYTZ0n9.js.br +0 -0
  158. package/dist/web/assets/process-table-NxYTZ0n9.js.gz +0 -0
  159. package/dist/web/assets/{proxy-settings-section-BCq7R5DA.js → proxy-settings-section-Exx7cQc4.js} +1 -1
  160. package/dist/web/assets/proxy-settings-section-Exx7cQc4.js.br +0 -0
  161. package/dist/web/assets/proxy-settings-section-Exx7cQc4.js.gz +0 -0
  162. package/dist/web/assets/{query-tab-DGvj_h6l.js → query-tab-BWtJPuxw.js} +1 -1
  163. package/dist/web/assets/query-tab-BWtJPuxw.js.br +0 -0
  164. package/dist/web/assets/{query-tab-DGvj_h6l.js.gz → query-tab-BWtJPuxw.js.gz} +0 -0
  165. package/dist/web/assets/{remote-access-settings-section-BrqOoOyk.js → remote-access-settings-section-C4K__DNW.js} +1 -1
  166. package/dist/web/assets/remote-access-settings-section-C4K__DNW.js.br +0 -0
  167. package/dist/web/assets/remote-access-settings-section-C4K__DNW.js.gz +0 -0
  168. package/dist/web/assets/{remote-desktop-clipboard-notice-5r6UbdlN.js → remote-desktop-clipboard-notice-DQyysBwL.js} +1 -1
  169. package/dist/web/assets/remote-desktop-clipboard-notice-DQyysBwL.js.br +0 -0
  170. package/dist/web/assets/remote-desktop-clipboard-notice-DQyysBwL.js.gz +0 -0
  171. package/dist/web/assets/{remote-desktop-mobile-sheet-B9Q4auqe.js → remote-desktop-mobile-sheet-DZtEFmgf.js} +2 -2
  172. package/dist/web/assets/remote-desktop-mobile-sheet-DZtEFmgf.js.br +0 -0
  173. package/dist/web/assets/remote-desktop-mobile-sheet-DZtEFmgf.js.gz +0 -0
  174. package/dist/web/assets/{remote-desktop-mobile-view-CL-jrkA8.js → remote-desktop-mobile-view-VW7HdPUX.js} +1 -1
  175. package/dist/web/assets/remote-desktop-mobile-view-VW7HdPUX.js.br +0 -0
  176. package/dist/web/assets/remote-desktop-mobile-view-VW7HdPUX.js.gz +0 -0
  177. package/dist/web/assets/{remote-desktop-readiness-gate-DXEf2Jnz.js → remote-desktop-readiness-gate-B3tuP9Kt.js} +1 -1
  178. package/dist/web/assets/remote-desktop-readiness-gate-B3tuP9Kt.js.br +0 -0
  179. package/dist/web/assets/remote-desktop-readiness-gate-B3tuP9Kt.js.gz +0 -0
  180. package/dist/web/assets/{remote-desktop-window-content-20P6-gKB.js → remote-desktop-window-content-n0NoDCaE.js} +1 -1
  181. package/dist/web/assets/remote-desktop-window-content-n0NoDCaE.js.br +0 -0
  182. package/dist/web/assets/remote-desktop-window-content-n0NoDCaE.js.gz +0 -0
  183. package/dist/web/assets/{rename-object-dialog-Bs7pUsUP.js → rename-object-dialog-rLTW4A55.js} +1 -1
  184. package/dist/web/assets/rename-object-dialog-rLTW4A55.js.br +0 -0
  185. package/dist/web/assets/rename-object-dialog-rLTW4A55.js.gz +0 -0
  186. package/dist/web/assets/{review-code-Bjo4sHN9.js → review-code-DNfsaNO1.js} +1 -1
  187. package/dist/web/assets/review-code-DNfsaNO1.js.br +0 -0
  188. package/dist/web/assets/review-code-DNfsaNO1.js.gz +0 -0
  189. package/dist/web/assets/{save-as-dialog-BHx0Wp2B.js → save-as-dialog-BSz8WrNZ.js} +1 -1
  190. package/dist/web/assets/save-as-dialog-BSz8WrNZ.js.br +0 -0
  191. package/dist/web/assets/save-as-dialog-BSz8WrNZ.js.gz +0 -0
  192. package/dist/web/assets/{schedules-settings-section-C6ATYDPK.js → schedules-settings-section-DqUZ-H2K.js} +1 -1
  193. package/dist/web/assets/schedules-settings-section-DqUZ-H2K.js.br +0 -0
  194. package/dist/web/assets/schedules-settings-section-DqUZ-H2K.js.gz +0 -0
  195. package/dist/web/assets/{session-review-tab-BPMUyNTx.js → session-review-tab-DKtrFb9y.js} +1 -1
  196. package/dist/web/assets/session-review-tab-DKtrFb9y.js.br +0 -0
  197. package/dist/web/assets/session-review-tab-DKtrFb9y.js.gz +0 -0
  198. package/dist/web/assets/{settings-body-Dy5D6Vu7.js → settings-body-C42xA9Dw.js} +1 -1
  199. package/dist/web/assets/settings-body-C42xA9Dw.js.br +0 -0
  200. package/dist/web/assets/settings-body-C42xA9Dw.js.gz +0 -0
  201. package/dist/web/assets/{settings-rows-BRoYE2To.js → settings-rows-C7ZatT9O.js} +1 -1
  202. package/dist/web/assets/settings-rows-C7ZatT9O.js.br +0 -0
  203. package/dist/web/assets/settings-rows-C7ZatT9O.js.gz +0 -0
  204. package/dist/web/assets/{settings-section-content-essUViKu.js → settings-section-content-B_iXJpcK.js} +2 -2
  205. package/dist/web/assets/settings-section-content-B_iXJpcK.js.br +0 -0
  206. package/dist/web/assets/settings-section-content-B_iXJpcK.js.gz +0 -0
  207. package/dist/web/assets/settings-section-content-rxfQEQ66.js +1 -0
  208. package/dist/web/assets/{settings-tab-Di1DUP1j.js → settings-tab-PRw6OpEb.js} +1 -1
  209. package/dist/web/assets/{settings-window-content-DYvYFPWy.js → settings-window-content-WITQsGfW.js} +1 -1
  210. package/dist/web/assets/{slash-command-picker-8da30mYP.js → slash-command-picker-DmDs8nOj.js} +1 -1
  211. package/dist/web/assets/slash-command-picker-DmDs8nOj.js.br +0 -0
  212. package/dist/web/assets/slash-command-picker-DmDs8nOj.js.gz +0 -0
  213. package/dist/web/assets/{sql-object-tab-DRIHVXuR.js → sql-object-tab-D6P4hzkb.js} +1 -1
  214. package/dist/web/assets/sql-object-tab-D6P4hzkb.js.br +0 -0
  215. package/dist/web/assets/sql-object-tab-D6P4hzkb.js.gz +0 -0
  216. package/dist/web/assets/{sqlite-viewer-G-Jl2yEr.js → sqlite-viewer-mUitnzHB.js} +1 -1
  217. package/dist/web/assets/sqlite-viewer-mUitnzHB.js.br +0 -0
  218. package/dist/web/assets/sqlite-viewer-mUitnzHB.js.gz +0 -0
  219. package/dist/web/assets/{structure-save-dialog-Cwikz0we.js → structure-save-dialog-CmbNlmBY.js} +1 -1
  220. package/dist/web/assets/structure-save-dialog-CmbNlmBY.js.br +0 -0
  221. package/dist/web/assets/structure-save-dialog-CmbNlmBY.js.gz +0 -0
  222. package/dist/web/assets/{structure-tab-CWEgdZPm.js → structure-tab-DCPNuFTe.js} +1 -1
  223. package/dist/web/assets/structure-tab-DCPNuFTe.js.br +0 -0
  224. package/dist/web/assets/structure-tab-DCPNuFTe.js.gz +0 -0
  225. package/dist/web/assets/{system-monitor-body-BDQm9-wb.js → system-monitor-body-BhxyqSqu.js} +2 -2
  226. package/dist/web/assets/system-monitor-body-BhxyqSqu.js.br +0 -0
  227. package/dist/web/assets/system-monitor-body-BhxyqSqu.js.gz +0 -0
  228. package/dist/web/assets/{system-monitor-tab-BouN21Yh.js → system-monitor-tab-B_miogQI.js} +1 -1
  229. package/dist/web/assets/{system-monitor-window-content-BRyWBSOb.js → system-monitor-window-content-8IogR6tR.js} +1 -1
  230. package/dist/web/assets/{tab-host-window-content-CZ55-bTz.js → tab-host-window-content-BWHQjgWZ.js} +1 -1
  231. package/dist/web/assets/tab-host-window-content-BWHQjgWZ.js.br +0 -0
  232. package/dist/web/assets/tab-host-window-content-BWHQjgWZ.js.gz +0 -0
  233. package/dist/web/assets/{table-tab-Bmn4UdBV.js → table-tab-E3YMueu_.js} +1 -1
  234. package/dist/web/assets/table-tab-E3YMueu_.js.br +0 -0
  235. package/dist/web/assets/table-tab-E3YMueu_.js.gz +0 -0
  236. package/dist/web/assets/{telegram-bot-token-field-fdHwRf39.js → telegram-bot-token-field-CwW24m62.js} +1 -1
  237. package/dist/web/assets/telegram-bot-token-field-CwW24m62.js.br +0 -0
  238. package/dist/web/assets/telegram-bot-token-field-CwW24m62.js.gz +0 -0
  239. package/dist/web/assets/{terminal-tab-D-qlZXLK.js → terminal-tab-BAe5cWEL.js} +1 -1
  240. package/dist/web/assets/terminal-tab-BAe5cWEL.js.br +0 -0
  241. package/dist/web/assets/terminal-tab-BAe5cWEL.js.gz +0 -0
  242. package/dist/web/assets/{tool-cards-BdXbQ9Fv.js → tool-cards-DrCiSH2r.js} +2 -2
  243. package/dist/web/assets/tool-cards-DrCiSH2r.js.br +0 -0
  244. package/dist/web/assets/tool-cards-DrCiSH2r.js.gz +0 -0
  245. package/dist/web/assets/{tools-settings-section-CcPaspbR.js → tools-settings-section-S-vBr2af.js} +1 -1
  246. package/dist/web/assets/tools-settings-section-S-vBr2af.js.br +0 -0
  247. package/dist/web/assets/tools-settings-section-S-vBr2af.js.gz +0 -0
  248. package/dist/web/assets/{use-inline-blame-Ck5XoSS3.js → use-inline-blame-BzVaaCCt.js} +1 -1
  249. package/dist/web/assets/use-inline-blame-BzVaaCCt.js.br +0 -0
  250. package/dist/web/assets/{use-inline-blame-Ck5XoSS3.js.gz → use-inline-blame-BzVaaCCt.js.gz} +0 -0
  251. package/dist/web/assets/{use-lsp-CGSqfFBk.js → use-lsp-BwDL9Fqk.js} +1 -1
  252. package/dist/web/assets/use-lsp-BwDL9Fqk.js.br +0 -0
  253. package/dist/web/assets/use-lsp-BwDL9Fqk.js.gz +0 -0
  254. package/dist/web/assets/{use-monaco-theme-BW05eZS4.js → use-monaco-theme-DljSc3Xo.js} +1 -1
  255. package/dist/web/assets/{use-process-kill-DJ_f7rCO.js → use-process-kill-YDcDqDtG.js} +1 -1
  256. package/dist/web/assets/use-process-kill-YDcDqDtG.js.br +0 -0
  257. package/dist/web/assets/use-process-kill-YDcDqDtG.js.gz +0 -0
  258. package/dist/web/assets/{use-sql-schema-info-wt320fVR.js → use-sql-schema-info-cAI0vn7d.js} +1 -1
  259. package/dist/web/assets/use-sql-schema-info-cAI0vn7d.js.br +0 -0
  260. package/dist/web/assets/use-sql-schema-info-cAI0vn7d.js.gz +0 -0
  261. package/dist/web/assets/{use-websocket-CTwe74Cb.js → use-websocket-Djo_ruX_.js} +1 -1
  262. package/dist/web/assets/{web-preview-tab-D4XayUEK.js → web-preview-tab-DXy0hRef.js} +1 -1
  263. package/dist/web/assets/web-preview-tab-DXy0hRef.js.br +0 -0
  264. package/dist/web/assets/web-preview-tab-DXy0hRef.js.gz +0 -0
  265. package/dist/web/index.html +2 -2
  266. package/dist/web/index.html.br +0 -0
  267. package/dist/web/index.html.gz +0 -0
  268. package/dist/web/sw.js +1 -1
  269. package/dist/web/sw.js.br +0 -0
  270. package/dist/web/sw.js.gz +0 -0
  271. package/docs/architecture/ai-chat-and-providers.md +29 -0
  272. package/docs/deployment-guide.md +39 -0
  273. package/docs/lessons-learned.md +14 -4
  274. package/package.json +1 -1
  275. package/scripts/mem-diag-report.ts +31 -0
  276. package/src/providers/claude-agent-sdk-query-options.ts +13 -2
  277. package/src/providers/claude-agent-sdk.ts +58 -18
  278. package/src/providers/claude-approval-queue.ts +75 -0
  279. package/src/providers/claude-effort-status.ts +211 -0
  280. package/src/providers/registry.ts +10 -1
  281. package/src/server/index.ts +7 -6
  282. package/src/server/middleware/access-log.ts +3 -0
  283. package/src/server/middleware/cors-headers.ts +44 -0
  284. package/src/server/routes/chat.ts +21 -1
  285. package/src/server/routes/database-impexp.ts +1 -1
  286. package/src/server/routes/design-preview.ts +2 -2
  287. package/src/server/routes/settings.ts +16 -0
  288. package/src/server/ws/chat-effort-status.ts +73 -0
  289. package/src/server/ws/chat.ts +33 -3
  290. package/src/services/compact-segment.ts +14 -19
  291. package/src/services/database/grid-export-tickets.ts +6 -2
  292. package/src/services/database/impexp/readers/file-text.ts +6 -3
  293. package/src/services/edge-forwarder.ts +12 -0
  294. package/src/services/file-chunks.ts +39 -0
  295. package/src/services/file-lines.ts +10 -17
  296. package/src/services/lsp/lsp-manager.ts +3 -0
  297. package/src/services/memory-diagnostics-native.ts +110 -0
  298. package/src/services/memory-diagnostics-report.ts +200 -0
  299. package/src/services/memory-diagnostics-requests.ts +95 -0
  300. package/src/services/memory-diagnostics-spawns.ts +242 -0
  301. package/src/services/memory-diagnostics.ts +121 -23
  302. package/src/services/memory-watchdog.ts +185 -0
  303. package/src/services/nested-subagent-spy.ts +3 -0
  304. package/src/services/process-thread-snapshot.ts +12 -2
  305. package/src/services/process-tree-memory.ts +161 -0
  306. package/src/services/read-lines.ts +16 -27
  307. package/src/services/supervisor.ts +1 -0
  308. package/src/services/terminal.service.ts +8 -0
  309. package/src/services/transcript-images-file.ts +7 -11
  310. package/src/shared/effort-status.ts +128 -0
  311. package/src/types/api.ts +3 -1
  312. package/src/types/chat.ts +8 -0
  313. package/src/web/components/chat/chat-scroll-nav.tsx +12 -8
  314. package/src/web/components/chat/chat-tab.tsx +15 -11
  315. package/src/web/components/chat/message-action-bar.tsx +19 -4
  316. package/src/web/components/chat/message-input.tsx +6 -0
  317. package/src/web/components/chat/message-list.tsx +55 -21
  318. package/src/web/components/chat/message-user-bubble.tsx +2 -2
  319. package/src/web/components/chat/model-thinking-selector-helpers.ts +79 -0
  320. package/src/web/components/chat/model-thinking-selector.tsx +36 -10
  321. package/src/web/components/chat/session-changes-context.tsx +29 -1
  322. package/src/web/components/layout/reparenting-tab.tsx +53 -14
  323. package/src/web/components/settings/ai-settings-section.tsx +32 -0
  324. package/src/web/components/settings/notifications-settings-section.tsx +14 -1
  325. package/src/web/hooks/use-chat.ts +16 -0
  326. package/src/web/hooks/use-effort-status.ts +46 -0
  327. package/src/web/hooks/use-relative-time-tick.ts +35 -0
  328. package/src/web/hooks/use-session-file-changes.ts +5 -1
  329. package/src/web/hooks/use-tab-overflow.ts +26 -13
  330. package/src/web/lib/format-date.ts +6 -1
  331. package/src/web/lib/web-push-client.ts +35 -5
  332. package/src/web/lib/web-push-support.ts +57 -4
  333. package/dist/web/assets/accounts-settings-section-DD88DRdx.js.br +0 -0
  334. package/dist/web/assets/accounts-settings-section-DD88DRdx.js.gz +0 -0
  335. package/dist/web/assets/agent-session-sheet-C0PhhLKR.js.br +0 -0
  336. package/dist/web/assets/agent-session-sheet-C0PhhLKR.js.gz +0 -0
  337. package/dist/web/assets/agent-session-window-content-D0XZgh4U.js.br +0 -0
  338. package/dist/web/assets/agent-session-window-content-D0XZgh4U.js.gz +0 -0
  339. package/dist/web/assets/ai-resource-editor-CpfQB6A7.js.br +0 -0
  340. package/dist/web/assets/ai-resource-editor-CpfQB6A7.js.gz +0 -0
  341. package/dist/web/assets/ai-settings-section-CQWBwWcM.js +0 -1
  342. package/dist/web/assets/ai-settings-section-CQWBwWcM.js.br +0 -0
  343. package/dist/web/assets/ai-settings-section-CQWBwWcM.js.gz +0 -0
  344. package/dist/web/assets/android-tab-4Z4AgHz1.js.br +0 -0
  345. package/dist/web/assets/android-tab-4Z4AgHz1.js.gz +0 -0
  346. package/dist/web/assets/appearance-settings-section-DjQjiJF7.js.br +0 -0
  347. package/dist/web/assets/appearance-settings-section-DjQjiJF7.js.gz +0 -0
  348. package/dist/web/assets/apps-panel-_lxY9xbc.js.br +0 -0
  349. package/dist/web/assets/apps-panel-_lxY9xbc.js.gz +0 -0
  350. package/dist/web/assets/branch-review-tab-BzrhnFSs.js.br +0 -0
  351. package/dist/web/assets/branch-review-tab-BzrhnFSs.js.gz +0 -0
  352. package/dist/web/assets/chat-tab-BKHepkYi.js.br +0 -0
  353. package/dist/web/assets/chat-tab-BKHepkYi.js.gz +0 -0
  354. package/dist/web/assets/chat-tab-Cv8gYzcA.js +0 -19
  355. package/dist/web/assets/chat-tab-Cv8gYzcA.js.br +0 -0
  356. package/dist/web/assets/chat-tab-Cv8gYzcA.js.gz +0 -0
  357. package/dist/web/assets/code-editor-COWmNXO4.js.br +0 -0
  358. package/dist/web/assets/code-editor-COWmNXO4.js.gz +0 -0
  359. package/dist/web/assets/codex-reset-credit-button-BHt4yJet.js.br +0 -0
  360. package/dist/web/assets/codex-reset-credit-button-BHt4yJet.js.gz +0 -0
  361. package/dist/web/assets/conflict-editor-tj-1gzKr.js.br +0 -0
  362. package/dist/web/assets/conflict-editor-tj-1gzKr.js.gz +0 -0
  363. package/dist/web/assets/connection-form-tab-BV2QX8Cf.js.br +0 -0
  364. package/dist/web/assets/connection-form-tab-BV2QX8Cf.js.gz +0 -0
  365. package/dist/web/assets/csv-preview-xhE2esgz.js.br +0 -0
  366. package/dist/web/assets/csv-preview-xhE2esgz.js.gz +0 -0
  367. package/dist/web/assets/db-tab-parts-C3MsjOID.js.br +0 -0
  368. package/dist/web/assets/db-tab-parts-C3MsjOID.js.gz +0 -0
  369. package/dist/web/assets/design-settings-section-C3LxYhIw.js.br +0 -0
  370. package/dist/web/assets/design-settings-section-C3LxYhIw.js.gz +0 -0
  371. package/dist/web/assets/design-tab-DYcvjdfb.js.br +0 -0
  372. package/dist/web/assets/design-tab-DYcvjdfb.js.gz +0 -0
  373. package/dist/web/assets/designs-sidebar-panel-Dpr4PPFT.js.br +0 -0
  374. package/dist/web/assets/designs-sidebar-panel-Dpr4PPFT.js.gz +0 -0
  375. package/dist/web/assets/diff-viewer-Fl_4jqr6.js.br +0 -0
  376. package/dist/web/assets/diff-viewer-Fl_4jqr6.js.gz +0 -0
  377. package/dist/web/assets/docx-preview-bVE9shPX.js.br +0 -0
  378. package/dist/web/assets/docx-preview-bVE9shPX.js.gz +0 -0
  379. package/dist/web/assets/editor-dialog-eEOYWdbr.js.br +0 -0
  380. package/dist/web/assets/editor-dialog-eEOYWdbr.js.gz +0 -0
  381. package/dist/web/assets/editor-language-service-D1_Ggq4s.js.br +0 -0
  382. package/dist/web/assets/editor-language-service-D1_Ggq4s.js.gz +0 -0
  383. package/dist/web/assets/explorer-body-C0UOcBPJ.js.br +0 -0
  384. package/dist/web/assets/explorer-body-C0UOcBPJ.js.gz +0 -0
  385. package/dist/web/assets/explorer-window-content-Dxee7SuJ.js.br +0 -0
  386. package/dist/web/assets/explorer-window-content-Dxee7SuJ.js.gz +0 -0
  387. package/dist/web/assets/extension-manager-section-D8krnoj8.js.br +0 -0
  388. package/dist/web/assets/extension-manager-section-D8krnoj8.js.gz +0 -0
  389. package/dist/web/assets/extension-webview-CtIdV69T.js.br +0 -0
  390. package/dist/web/assets/extension-webview-CtIdV69T.js.gz +0 -0
  391. package/dist/web/assets/files-settings-section-C8vtarR8.js.br +0 -0
  392. package/dist/web/assets/files-settings-section-C8vtarR8.js.gz +0 -0
  393. package/dist/web/assets/format-date-CSHaU2wN.js +0 -1
  394. package/dist/web/assets/general-settings-section-Chu8Ut8h.js.br +0 -0
  395. package/dist/web/assets/general-settings-section-Chu8Ut8h.js.gz +0 -0
  396. package/dist/web/assets/git-log-panel-DG09p6k5.js.br +0 -0
  397. package/dist/web/assets/git-log-panel-DG09p6k5.js.gz +0 -0
  398. package/dist/web/assets/git-review-tab-CSy8DjSC.js.br +0 -0
  399. package/dist/web/assets/git-review-tab-CSy8DjSC.js.gz +0 -0
  400. package/dist/web/assets/glide-data-grid-BN1y4BT0.js.br +0 -0
  401. package/dist/web/assets/glide-data-grid-BN1y4BT0.js.gz +0 -0
  402. package/dist/web/assets/grid-save-dialog-CfSuWw2t.js.br +0 -0
  403. package/dist/web/assets/grid-save-dialog-CfSuWw2t.js.gz +0 -0
  404. package/dist/web/assets/group-chat-tab-B04QMp7A.js.br +0 -0
  405. package/dist/web/assets/group-chat-tab-B04QMp7A.js.gz +0 -0
  406. package/dist/web/assets/impexp-tab-BRNouquR.js.br +0 -0
  407. package/dist/web/assets/impexp-tab-BRNouquR.js.gz +0 -0
  408. package/dist/web/assets/index-BpgSIM5B.js.br +0 -0
  409. package/dist/web/assets/index-BpgSIM5B.js.gz +0 -0
  410. package/dist/web/assets/keyboard-shortcuts-section-BKDyZHhK.js.br +0 -0
  411. package/dist/web/assets/keyboard-shortcuts-section-BKDyZHhK.js.gz +0 -0
  412. package/dist/web/assets/language-servers-section-CbbkezhA.js.br +0 -0
  413. package/dist/web/assets/language-servers-section-CbbkezhA.js.gz +0 -0
  414. package/dist/web/assets/logs-app-Blr1-v4q.js.br +0 -0
  415. package/dist/web/assets/logs-app-Blr1-v4q.js.gz +0 -0
  416. package/dist/web/assets/lsp-client-Ds3U2O52.js.br +0 -0
  417. package/dist/web/assets/lsp-client-Ds3U2O52.js.gz +0 -0
  418. package/dist/web/assets/markdown-renderer-DzCPadpB.js.br +0 -0
  419. package/dist/web/assets/markdown-renderer-DzCPadpB.js.gz +0 -0
  420. package/dist/web/assets/mobile-explorer-sheet-BClCMXb8.js.br +0 -0
  421. package/dist/web/assets/mobile-explorer-sheet-BClCMXb8.js.gz +0 -0
  422. package/dist/web/assets/new-design-dialog-HRP9H11v.js.br +0 -0
  423. package/dist/web/assets/new-design-dialog-HRP9H11v.js.gz +0 -0
  424. package/dist/web/assets/notifications-settings-section-CYTTQaNr.js +0 -1
  425. package/dist/web/assets/notifications-settings-section-CYTTQaNr.js.br +0 -0
  426. package/dist/web/assets/notifications-settings-section-CYTTQaNr.js.gz +0 -0
  427. package/dist/web/assets/ppmbot-settings-section-BRowIIqc.js.br +0 -0
  428. package/dist/web/assets/ppmbot-settings-section-BRowIIqc.js.gz +0 -0
  429. package/dist/web/assets/problems-panel-DdB10Ybd.js.br +0 -0
  430. package/dist/web/assets/problems-panel-DdB10Ybd.js.gz +0 -0
  431. package/dist/web/assets/process-table-CzLaWPe0.js.br +0 -0
  432. package/dist/web/assets/process-table-CzLaWPe0.js.gz +0 -0
  433. package/dist/web/assets/proxy-settings-section-BCq7R5DA.js.br +0 -0
  434. package/dist/web/assets/proxy-settings-section-BCq7R5DA.js.gz +0 -0
  435. package/dist/web/assets/query-tab-DGvj_h6l.js.br +0 -0
  436. package/dist/web/assets/remote-access-settings-section-BrqOoOyk.js.br +0 -0
  437. package/dist/web/assets/remote-access-settings-section-BrqOoOyk.js.gz +0 -0
  438. package/dist/web/assets/remote-desktop-clipboard-notice-5r6UbdlN.js.br +0 -0
  439. package/dist/web/assets/remote-desktop-clipboard-notice-5r6UbdlN.js.gz +0 -0
  440. package/dist/web/assets/remote-desktop-mobile-sheet-B9Q4auqe.js.br +0 -0
  441. package/dist/web/assets/remote-desktop-mobile-sheet-B9Q4auqe.js.gz +0 -0
  442. package/dist/web/assets/remote-desktop-mobile-view-CL-jrkA8.js.br +0 -0
  443. package/dist/web/assets/remote-desktop-mobile-view-CL-jrkA8.js.gz +0 -0
  444. package/dist/web/assets/remote-desktop-readiness-gate-DXEf2Jnz.js.br +0 -0
  445. package/dist/web/assets/remote-desktop-readiness-gate-DXEf2Jnz.js.gz +0 -0
  446. package/dist/web/assets/remote-desktop-window-content-20P6-gKB.js.br +0 -0
  447. package/dist/web/assets/remote-desktop-window-content-20P6-gKB.js.gz +0 -0
  448. package/dist/web/assets/rename-object-dialog-Bs7pUsUP.js.br +0 -0
  449. package/dist/web/assets/rename-object-dialog-Bs7pUsUP.js.gz +0 -0
  450. package/dist/web/assets/review-code-Bjo4sHN9.js.br +0 -0
  451. package/dist/web/assets/review-code-Bjo4sHN9.js.gz +0 -0
  452. package/dist/web/assets/save-as-dialog-BHx0Wp2B.js.br +0 -0
  453. package/dist/web/assets/save-as-dialog-BHx0Wp2B.js.gz +0 -0
  454. package/dist/web/assets/schedules-settings-section-C6ATYDPK.js.br +0 -0
  455. package/dist/web/assets/schedules-settings-section-C6ATYDPK.js.gz +0 -0
  456. package/dist/web/assets/session-review-tab-BPMUyNTx.js.br +0 -0
  457. package/dist/web/assets/session-review-tab-BPMUyNTx.js.gz +0 -0
  458. package/dist/web/assets/settings-body-Dy5D6Vu7.js.br +0 -0
  459. package/dist/web/assets/settings-body-Dy5D6Vu7.js.gz +0 -0
  460. package/dist/web/assets/settings-rows-BRoYE2To.js.br +0 -0
  461. package/dist/web/assets/settings-rows-BRoYE2To.js.gz +0 -0
  462. package/dist/web/assets/settings-section-content-C4DX7TYv.js +0 -1
  463. package/dist/web/assets/settings-section-content-essUViKu.js.br +0 -0
  464. package/dist/web/assets/settings-section-content-essUViKu.js.gz +0 -0
  465. package/dist/web/assets/slash-command-picker-8da30mYP.js.br +0 -0
  466. package/dist/web/assets/slash-command-picker-8da30mYP.js.gz +0 -0
  467. package/dist/web/assets/sql-object-tab-DRIHVXuR.js.br +0 -0
  468. package/dist/web/assets/sql-object-tab-DRIHVXuR.js.gz +0 -0
  469. package/dist/web/assets/sqlite-viewer-G-Jl2yEr.js.br +0 -0
  470. package/dist/web/assets/sqlite-viewer-G-Jl2yEr.js.gz +0 -0
  471. package/dist/web/assets/structure-save-dialog-Cwikz0we.js.br +0 -0
  472. package/dist/web/assets/structure-save-dialog-Cwikz0we.js.gz +0 -0
  473. package/dist/web/assets/structure-tab-CWEgdZPm.js.br +0 -0
  474. package/dist/web/assets/structure-tab-CWEgdZPm.js.gz +0 -0
  475. package/dist/web/assets/system-monitor-body-BDQm9-wb.js.br +0 -0
  476. package/dist/web/assets/system-monitor-body-BDQm9-wb.js.gz +0 -0
  477. package/dist/web/assets/tab-host-window-content-CZ55-bTz.js.br +0 -0
  478. package/dist/web/assets/tab-host-window-content-CZ55-bTz.js.gz +0 -0
  479. package/dist/web/assets/table-tab-Bmn4UdBV.js.br +0 -0
  480. package/dist/web/assets/table-tab-Bmn4UdBV.js.gz +0 -0
  481. package/dist/web/assets/telegram-bot-token-field-fdHwRf39.js.br +0 -0
  482. package/dist/web/assets/telegram-bot-token-field-fdHwRf39.js.gz +0 -0
  483. package/dist/web/assets/terminal-tab-D-qlZXLK.js.br +0 -0
  484. package/dist/web/assets/terminal-tab-D-qlZXLK.js.gz +0 -0
  485. package/dist/web/assets/tool-cards-BdXbQ9Fv.js.br +0 -0
  486. package/dist/web/assets/tool-cards-BdXbQ9Fv.js.gz +0 -0
  487. package/dist/web/assets/tools-settings-section-CcPaspbR.js.br +0 -0
  488. package/dist/web/assets/tools-settings-section-CcPaspbR.js.gz +0 -0
  489. package/dist/web/assets/use-inline-blame-Ck5XoSS3.js.br +0 -0
  490. package/dist/web/assets/use-lsp-CGSqfFBk.js.br +0 -0
  491. package/dist/web/assets/use-lsp-CGSqfFBk.js.gz +0 -0
  492. package/dist/web/assets/use-process-kill-DJ_f7rCO.js.br +0 -0
  493. package/dist/web/assets/use-process-kill-DJ_f7rCO.js.gz +0 -0
  494. package/dist/web/assets/use-sql-schema-info-wt320fVR.js.br +0 -0
  495. package/dist/web/assets/use-sql-schema-info-wt320fVR.js.gz +0 -0
  496. package/dist/web/assets/web-preview-tab-D4XayUEK.js.br +0 -0
  497. package/dist/web/assets/web-preview-tab-D4XayUEK.js.gz +0 -0
@@ -1,19 +1,37 @@
1
- import { appendFileSync, mkdirSync, writeFileSync } from "node:fs";
1
+ import { appendFileSync, mkdirSync, renameSync, statSync, writeFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { heapStats } from "bun:jsc";
4
4
  import { getPpmDir } from "./ppm-dir.ts";
5
+ import { readNativeMemory } from "./memory-diagnostics-native.ts";
6
+ import { spawnTracker } from "./memory-diagnostics-spawns.ts";
7
+ import { diffRequestCounts, requestTracker } from "./memory-diagnostics-requests.ts";
8
+ import { startMemoryWatchdog, summarizeDescendants } from "./memory-watchdog.ts";
5
9
 
6
10
  /**
7
- * Opt-in memory monitor for chasing server growth (`PPM_MEM_DIAG=1`).
11
+ * Memory monitoring for each long-lived PPM process (server, supervisor, edge forwarder).
8
12
  *
9
- * Every interval it appends one JSON line to `<ppm dir>/mem-diag.log`: RSS, the JS heap,
10
- * the object types that grew most since the previous sample, and any gauges registered by
11
- * long-lived state (session maps, buffered events). Comparing RSS against `heapSize` is the
12
- * first fork in the investigation — RSS climbing with a flat heap is native memory (child
13
- * process pipes, Bun internals), which a heap snapshot cannot show.
13
+ * Always on: `memory-watchdog.ts`, a summary line in `ppm.log` every 30 minutes plus a WARN
14
+ * at each private-bytes threshold. Opt-in (`PPM_MEM_DIAG=1`): the detailed monitor below.
14
15
  *
15
- * A V8-format heap snapshot is written the first time the heap crosses each threshold, so
16
- * two snapshots from the same run can be diffed in Chrome DevTools (Memory → Comparison).
16
+ * Every interval it appends one JSON line to `<ppm dir>/mem-diag.log` (the supervisor
17
+ * writes `mem-diag-supervisor.log`): RSS, the JS heap, **private bytes** (the commit charge
18
+ * that actually runs a machine out of memory — RSS alone hid a 32 GB leak), kernel handles,
19
+ * the child processes started since the previous line and those still alive, the memory of
20
+ * every process below this one (by program, and by direct child with the chat session it
21
+ * serves), the HTTP routes served since the previous line, the object
22
+ * types that grew most, a few absolute counts of objects that own native resources, and
23
+ * any gauges registered by long-lived state (session maps, buffered events).
24
+ *
25
+ * How to read it: `bun scripts/mem-diag-report.ts` correlates private-byte growth with
26
+ * spawns and gauges. By hand, the first fork is still private vs heap — private climbing
27
+ * with a flat heap is native memory (child process pipes, allocator segments), which a
28
+ * heap snapshot cannot show; then look at what `spawned` and `gauges` did in the lines
29
+ * where `privateDeltaMB` jumps.
30
+ *
31
+ * Two kinds of one-off lines: a V8 heap snapshot the first time the heap crosses each
32
+ * threshold (diffable in Chrome DevTools → Memory → Comparison), and an `alert` line the
33
+ * first time private bytes cross each threshold, carrying the oldest live children and the
34
+ * all-time spawn totals — the state a later reader can no longer reconstruct.
17
35
  */
18
36
 
19
37
  const gauges = new Map<string, () => number>();
@@ -23,23 +41,59 @@ export function registerMemoryGauge(name: string, read: () => number): void {
23
41
  gauges.set(name, read);
24
42
  }
25
43
 
44
+ function readGauges(): Record<string, number | string> {
45
+ const values: Record<string, number | string> = {};
46
+ for (const [name, read] of gauges) {
47
+ try { values[name] = read(); } catch (e) { values[name] = `err: ${(e as Error).message}`; }
48
+ }
49
+ return values;
50
+ }
51
+
26
52
  const MB = 1024 * 1024;
27
53
  const SNAPSHOT_THRESHOLDS_MB = [1024, 2048, 4096, 8192];
54
+ const PRIVATE_ALERT_THRESHOLDS_MB = [2048, 4096, 8192, 16384, 24576, 32768];
55
+ /** Object types that each hold something outside the JS heap. Absolute counts, every line. */
56
+ const NATIVE_OWNER_TYPES = ["Subprocess", "FSWatcher", "SQLStatement", "ArrayBuffer", "ReadableStream", "WebSocket", "ServerWebSocket", "Timeout"];
57
+ /** ~1 KB a line at 30 s is ~3 MB a day; one rotated copy keeps about two weeks. */
58
+ const MAX_LOG_BYTES = 50 * MB;
59
+
60
+ export interface MemoryDiagnosticsOptions {
61
+ /** Which process this is; anything but `server` gets its own log file. */
62
+ role?: "server" | "supervisor" | "edge";
63
+ }
64
+
65
+ let started = false;
28
66
 
29
- export function startMemoryDiagnostics(): void {
67
+ export function startMemoryDiagnostics(opts: MemoryDiagnosticsOptions = {}): void {
68
+ if (started) return;
69
+ started = true;
70
+ const role = opts.role ?? "server";
71
+ spawnTracker.install();
72
+ startMemoryWatchdog(role, readGauges);
30
73
  if (process.env.PPM_MEM_DIAG !== "1") return;
31
74
  const intervalMs = Math.max(5_000, Number(process.env.PPM_MEM_DIAG_INTERVAL_MS) || 30_000);
32
75
  const dir = getPpmDir();
33
- const logPath = join(dir, "mem-diag.log");
76
+ const logPath = join(dir, role === "server" ? "mem-diag.log" : `mem-diag-${role}.log`);
34
77
  const snapDir = join(dir, "heap-snapshots");
35
78
  const taken = new Set<number>();
79
+ const alerted = new Set<number>();
36
80
  let lastCounts: Record<string, number> = {};
81
+ let lastPrivateMB: number | undefined;
37
82
  const startedAt = Date.now();
83
+ let lastRequests = requestTracker.read();
38
84
 
39
- const sample = () => {
85
+ const write = (obj: unknown) => {
86
+ try {
87
+ if (statSync(logPath).size > MAX_LOG_BYTES) renameSync(logPath, `${logPath}.1`);
88
+ } catch { /* no file yet */ }
89
+ appendFileSync(logPath, JSON.stringify(obj) + "\n");
90
+ };
91
+
92
+ const sample = async () => {
40
93
  try {
41
94
  const mem = process.memoryUsage();
42
95
  const stats = heapStats();
96
+ const native = await readNativeMemory();
43
97
  const counts = stats.objectTypeCounts as Record<string, number>;
44
98
  const grew = Object.entries(counts)
45
99
  .map(([type, n]) => [type, n - (lastCounts[type] ?? 0)] as const)
@@ -48,32 +102,76 @@ export function startMemoryDiagnostics(): void {
48
102
  .slice(0, 12);
49
103
  lastCounts = counts;
50
104
 
51
- const gaugeValues: Record<string, number | string> = {};
52
- for (const [name, read] of gauges) {
53
- try { gaugeValues[name] = read(); } catch (e) { gaugeValues[name] = `err: ${(e as Error).message}`; }
54
- }
105
+ const nativeOwners: Record<string, number> = {};
106
+ for (const type of NATIVE_OWNER_TYPES) if (counts[type] !== undefined) nativeOwners[type] = counts[type];
55
107
 
56
- const line = {
108
+ const spawns = spawnTracker.snapshot();
109
+ const gaugeValues = readGauges();
110
+ const children = await summarizeDescendants(process.pid, 10);
111
+ const requestsNow = requestTracker.read();
112
+ const requests = diffRequestCounts(lastRequests, requestsNow, 10);
113
+ lastRequests = requestsNow;
114
+ const privateDeltaMB = native.privateMB !== undefined && lastPrivateMB !== undefined
115
+ ? native.privateMB - lastPrivateMB
116
+ : undefined;
117
+ if (native.privateMB !== undefined) lastPrivateMB = native.privateMB;
118
+
119
+ write({
57
120
  t: new Date().toISOString(),
121
+ role,
122
+ pid: process.pid,
58
123
  upMin: Math.round((Date.now() - startedAt) / 60_000),
59
124
  rssMB: Math.round(mem.rss / MB),
125
+ ...native,
126
+ privateDeltaMB,
60
127
  heapMB: Math.round(stats.heapSize / MB),
128
+ heapCapacityMB: Math.round(stats.heapCapacity / MB),
129
+ extraMB: Math.round(stats.extraMemorySize / MB),
61
130
  externalMB: Math.round(mem.external / MB),
62
131
  arrayBuffersMB: Math.round(mem.arrayBuffers / MB),
63
132
  objects: stats.objectCount,
133
+ protectedObjects: stats.protectedObjectCount,
134
+ spawned: spawns.spawned,
135
+ liveChildren: spawns.liveTotal,
136
+ liveByCommand: spawns.live,
137
+ spawnTotal: spawns.total,
138
+ children,
139
+ requests,
140
+ nativeOwners,
64
141
  grew: Object.fromEntries(grew),
65
142
  gauges: gaugeValues,
66
- };
67
- appendFileSync(logPath, JSON.stringify(line) + "\n");
143
+ });
144
+
145
+ if (native.privateMB !== undefined) {
146
+ for (const threshold of PRIVATE_ALERT_THRESHOLDS_MB) {
147
+ if (native.privateMB < threshold || alerted.has(threshold)) continue;
148
+ alerted.add(threshold);
149
+ write({
150
+ t: new Date().toISOString(),
151
+ role,
152
+ pid: process.pid,
153
+ alert: "private-bytes",
154
+ thresholdMB: threshold,
155
+ privateMB: native.privateMB,
156
+ rssMB: Math.round(mem.rss / MB),
157
+ heapMB: Math.round(stats.heapSize / MB),
158
+ handles: native.handles,
159
+ spawnTotals: spawnTracker.topTotals(40),
160
+ oldestLiveChildren: spawnTracker.oldestLive(40),
161
+ children,
162
+ gauges: gaugeValues,
163
+ });
164
+ }
165
+ }
68
166
 
69
167
  const heapMB = stats.heapSize / MB;
70
168
  for (const threshold of SNAPSHOT_THRESHOLDS_MB) {
71
169
  if (heapMB < threshold || taken.has(threshold)) continue;
72
170
  taken.add(threshold);
73
171
  mkdirSync(snapDir, { recursive: true });
74
- const file = join(snapDir, `heap-${threshold}MB-${Date.now()}.heapsnapshot`);
172
+ const file = join(snapDir, `heap-${role}-${threshold}MB-${Date.now()}.heapsnapshot`);
75
173
  writeFileSync(file, Bun.generateHeapSnapshot("v8"));
76
- appendFileSync(logPath, JSON.stringify({ t: new Date().toISOString(), snapshot: file }) + "\n");
174
+ write({ t: new Date().toISOString(), role, snapshot: file });
77
175
  }
78
176
  } catch (e) {
79
177
  console.warn(`[mem-diag] sample failed: ${(e as Error).message}`);
@@ -81,6 +179,6 @@ export function startMemoryDiagnostics(): void {
81
179
  };
82
180
 
83
181
  console.log(`[mem-diag] sampling every ${intervalMs}ms → ${logPath}`);
84
- sample();
85
- setInterval(sample, intervalMs).unref?.();
182
+ void sample();
183
+ setInterval(() => { void sample(); }, intervalMs).unref?.();
86
184
  }
@@ -0,0 +1,185 @@
1
+ /**
2
+ * Always-on memory watchdog: a few lines in `ppm.log`, so that "why is PPM using so much RAM"
3
+ * has an answer in the log a user already has, without anyone having set `PPM_MEM_DIAG`.
4
+ *
5
+ * Every minute it reads this process's private bytes and those of every process below it
6
+ * (one OS call, ~8 ms on Windows). It writes:
7
+ * - a summary line every 30 minutes — this process, its children by program, the biggest child
8
+ * branches with the chat session each one serves, the busiest HTTP routes since the last
9
+ * summary, and the registered gauges (sessions, clients, terminals…). WARN instead of INFO
10
+ * when private bytes grew by a gigabyte or more since the previous summary;
11
+ * - a WARN the first time private bytes (and, for the server, its children's total) cross each
12
+ * threshold, with the same detail;
13
+ * - an INFO when a Claude/Codex subprocess starts or exits, with its pid, its session and what
14
+ * it held at its last reading — the pid in the summary is then traceable to a chat.
15
+ *
16
+ * What it answered on 26/10/2026, after the fact and by hand: the server at 7 GB while its JS
17
+ * heap was 186 MB (a native leak), and 17 Claude subprocesses at ~650 MB each (by design, one
18
+ * per open chat). Both halves are in one summary line now.
19
+ *
20
+ * The detailed per-30-second JSON stays behind `PPM_MEM_DIAG=1` (`memory-diagnostics.ts`).
21
+ */
22
+ import { createLogger } from "./logger.ts";
23
+ import { readNativeMemory } from "./memory-diagnostics-native.ts";
24
+ import { spawnTracker } from "./memory-diagnostics-spawns.ts";
25
+ import { diffRequestCounts, requestTracker } from "./memory-diagnostics-requests.ts";
26
+ import { descendantsOf, readProcessList } from "./process-tree-memory.ts";
27
+ import { heapStats } from "bun:jsc";
28
+
29
+ const log = createLogger("mem");
30
+
31
+ const MB = 1024 * 1024;
32
+ const WATCH_INTERVAL_MS = 60_000;
33
+ const SUMMARY_INTERVAL_MS = 30 * 60_000;
34
+ /** Growth between two summaries that turns the summary into a WARN. */
35
+ const FAST_GROWTH_MB = 1024;
36
+ const SELF_ALERT_MB = [2048, 4096, 8192, 16384, 32768];
37
+ const CHILDREN_ALERT_MB = [8192, 16384, 24576, 32768];
38
+
39
+ export interface ChildBranch {
40
+ pid: number;
41
+ name: string;
42
+ /** The tracker's label for a direct child, when it saw the spawn. */
43
+ label?: string;
44
+ session?: string;
45
+ ageMin?: number;
46
+ /** The child alone. */
47
+ ownMB: number;
48
+ /** The child and everything below it. */
49
+ treeMB: number;
50
+ }
51
+
52
+ export interface TreeSummary {
53
+ count: number;
54
+ privateMB: number;
55
+ byName: Record<string, { n: number; mb: number }>;
56
+ /** Direct children, biggest subtree first. */
57
+ branches: ChildBranch[];
58
+ }
59
+
60
+ /** Read the tree below `rootPid` and fold it by program and by direct child. Undefined when unreadable. */
61
+ export async function summarizeDescendants(rootPid: number, limit: number): Promise<TreeSummary | undefined> {
62
+ const list = await readProcessList(rootPid);
63
+ if (!list) return undefined;
64
+ const all = descendantsOf(rootPid, list);
65
+ const byName: TreeSummary["byName"] = {};
66
+ const branches = new Map<number, ChildBranch>();
67
+ let privateMB = 0;
68
+ for (const d of all) {
69
+ privateMB += d.privateMB;
70
+ const n = (byName[d.name] ??= { n: 0, mb: 0 });
71
+ n.n++;
72
+ n.mb += d.privateMB;
73
+ if (d.depth === 1) {
74
+ const tracked = spawnTracker.describe(d.pid);
75
+ if (tracked) tracked.lastPrivateMB = d.privateMB;
76
+ branches.set(d.pid, {
77
+ pid: d.pid, name: d.name, label: tracked?.label, session: tracked?.session,
78
+ ageMin: tracked ? Math.round((Date.now() - tracked.startedAt) / 60_000) : undefined,
79
+ ownMB: d.privateMB, treeMB: 0,
80
+ });
81
+ }
82
+ }
83
+ for (const d of all) {
84
+ const b = branches.get(d.branchPid);
85
+ if (b) b.treeMB += d.privateMB;
86
+ }
87
+ return {
88
+ count: all.length,
89
+ privateMB,
90
+ byName,
91
+ branches: [...branches.values()].sort((a, b) => b.treeMB - a.treeMB).slice(0, limit),
92
+ };
93
+ }
94
+
95
+ const plural = (n: number, mb: number) => `${n > 1 ? `×${n} ` : ""}${mb}MB`;
96
+
97
+ /** The children half of a log line. Exported for tests. */
98
+ export function describeTree(tree: TreeSummary | undefined): string {
99
+ if (!tree) return "children unreadable";
100
+ if (tree.count === 0) return "no children";
101
+ const names = Object.entries(tree.byName)
102
+ .sort((a, b) => b[1].mb - a[1].mb).slice(0, 6)
103
+ .map(([name, v]) => `${name} ${plural(v.n, v.mb)}`).join(", ");
104
+ const biggest = tree.branches.slice(0, 5).map((b) => {
105
+ const who = b.session ? `session=${b.session.slice(0, 8)}` : b.label ?? b.name;
106
+ const own = b.treeMB !== b.ownMB ? ` (own ${b.ownMB}MB)` : "";
107
+ const age = b.ageMin !== undefined ? ` ${b.ageMin}m` : "";
108
+ return `${b.name} pid=${b.pid} ${who}${age} ${b.treeMB}MB${own}`;
109
+ }).join("; ");
110
+ return `children ${tree.count} = ${tree.privateMB}MB [${names}] biggest: ${biggest}`;
111
+ }
112
+
113
+ function describeRequests(top: Record<string, { n: number; mb: number; streamed: number }>, total: number): string {
114
+ const rows = Object.entries(top).map(([k, v]) => `${k} ×${v.n}${v.mb ? ` ${v.mb}MB` : ""}${v.streamed ? ` (${v.streamed} streamed)` : ""}`);
115
+ return `requests ${total}${rows.length ? `: ${rows.join(", ")}` : ""}`;
116
+ }
117
+
118
+ function describeGauges(gauges: Record<string, number | string>): string {
119
+ const rows = Object.entries(gauges).map(([k, v]) => `${k}=${v}`);
120
+ return rows.length ? `gauges ${rows.join(" ")}` : "";
121
+ }
122
+
123
+ export function startMemoryWatchdog(role: string, readGauges: () => Record<string, number | string>): void {
124
+ const startedAt = Date.now();
125
+ const selfAlerted = new Set<number>();
126
+ const childAlerted = new Set<number>();
127
+ // The first summary comes a minute after start: the baseline every later line is read against.
128
+ let lastSummaryAt = Date.now() - SUMMARY_INTERVAL_MS + WATCH_INTERVAL_MS;
129
+ let lastSummaryPrivateMB: number | undefined;
130
+ let lastRequests = requestTracker.read();
131
+
132
+ spawnTracker.onLifecycle = (e) => {
133
+ const who = `${e.label} pid=${e.pid}${e.session ? ` session=${e.session}` : ""}`;
134
+ if (e.event === "start") log.info(`${who} started`);
135
+ else log.info(`${who} exited after ${e.ageMin}m${e.lastPrivateMB !== undefined ? `, held ${e.lastPrivateMB}MB at its last reading` : ""}`);
136
+ };
137
+
138
+ const tick = async () => {
139
+ try {
140
+ const native = await readNativeMemory();
141
+ const tree = await summarizeDescendants(process.pid, 5);
142
+ const self = native.privateMB;
143
+ const crossedSelf = self === undefined ? [] : SELF_ALERT_MB.filter((t) => self >= t && !selfAlerted.has(t));
144
+ const crossedChildren = role === "server" && tree
145
+ ? CHILDREN_ALERT_MB.filter((t) => tree.privateMB >= t && !childAlerted.has(t))
146
+ : [];
147
+ const summaryDue = Date.now() - lastSummaryAt >= SUMMARY_INTERVAL_MS;
148
+ if (!crossedSelf.length && !crossedChildren.length && !summaryDue) return;
149
+
150
+ const heapMB = Math.round(heapStats().heapSize / MB);
151
+ const rssMB = Math.round(process.memoryUsage().rss / MB);
152
+ const upMin = Math.round((Date.now() - startedAt) / 60_000);
153
+ const head = `${role} pid=${process.pid} up=${upMin}m private=${self ?? "?"}MB heap=${heapMB}MB rss=${rssMB}MB handles=${native.handles ?? "?"}`;
154
+ const tail = [describeTree(tree), describeGauges(readGauges())].filter(Boolean).join(" | ");
155
+
156
+ // One line per crossing, naming the highest threshold passed since the last tick.
157
+ if (crossedSelf.length) {
158
+ for (const t of crossedSelf) selfAlerted.add(t);
159
+ log.warn(`${role} private bytes crossed ${Math.max(...crossedSelf)}MB (heap ${heapMB}MB, so ~${Math.max(0, (self ?? 0) - heapMB)}MB is native) — ${head} | ${tail}`);
160
+ }
161
+ if (crossedChildren.length) {
162
+ for (const t of crossedChildren) childAlerted.add(t);
163
+ log.warn(`${role} children crossed ${Math.max(...crossedChildren)}MB together — ${head} | ${tail}`);
164
+ }
165
+ if (!summaryDue) return;
166
+
167
+ const now = requestTracker.read();
168
+ const requests = diffRequestCounts(lastRequests, now, 6);
169
+ lastRequests = now;
170
+ const minutes = Math.round((Date.now() - lastSummaryAt) / 60_000);
171
+ const growth = self !== undefined && lastSummaryPrivateMB !== undefined ? self - lastSummaryPrivateMB : undefined;
172
+ lastSummaryAt = Date.now();
173
+ if (self !== undefined) lastSummaryPrivateMB = self;
174
+ const growthText = growth === undefined ? "" : ` (${growth >= 0 ? "+" : ""}${growth}MB in ${minutes}m)`;
175
+ const reqText = requests.total ? ` | ${describeRequests(requests.top, requests.total)}` : "";
176
+ const line = `summary ${head}${growthText} | ${tail}${reqText}`;
177
+ if (growth !== undefined && growth >= FAST_GROWTH_MB) log.warn(`${line} — private bytes grew fast`);
178
+ else log.info(line);
179
+ } catch (e) {
180
+ log.debug(`watchdog tick failed: ${(e as Error).message}`);
181
+ }
182
+ };
183
+
184
+ setInterval(() => { void tick(); }, WATCH_INTERVAL_MS).unref?.();
185
+ }
@@ -30,6 +30,7 @@ import type { ChatEvent } from "../types/chat.ts";
30
30
  import { createAgentTranscriptLineParser } from "./subagent-transcript-merger.ts";
31
31
  import { getCachedSubagentGroups } from "./agent-transcript/agent-transcript-index-cache.ts";
32
32
  import { createLogger } from "./logger.ts";
33
+ import { registerMemoryGauge } from "./memory-diagnostics.ts";
33
34
 
34
35
  const log = createLogger("nested-spy");
35
36
 
@@ -207,3 +208,5 @@ function stopAllForSession(sessionId: string): void {
207
208
  }
208
209
 
209
210
  export const nestedSubagentSpy = { startSpy, stopSpy, stopAllForSession };
211
+
212
+ registerMemoryGauge("nestedSubagentSpy.sessions", () => sessions.size);
@@ -109,7 +109,12 @@ const STATUS_INFO_LENGTH_MISMATCH = 0xc0000004 | 0;
109
109
  /** Grows on a length mismatch; a busy machine needs ~1-2 MB. */
110
110
  let bufferSize = 2 * 1024 * 1024;
111
111
 
112
- async function readWindows(pid: number): Promise<ThreadSnapshot | null> {
112
+ /**
113
+ * One `SystemProcessInformation` reading of every process on the machine, or null when it
114
+ * cannot be taken. `base` is the buffer's address: the image names inside it are pointers
115
+ * into the same buffer, so `pointer - base` is their offset in `view`.
116
+ */
117
+ export async function querySystemProcessInformation(): Promise<{ view: DataView; base: bigint } | null> {
113
118
  if (ntdll === undefined) {
114
119
  try {
115
120
  const { dlopen, FFIType } = await import("bun:ffi");
@@ -131,11 +136,16 @@ async function readWindows(pid: number): Promise<ThreadSnapshot | null> {
131
136
  continue;
132
137
  }
133
138
  if (status !== 0) return null;
134
- return parseWindowsProcessInformation(new DataView(buf.buffer), pid);
139
+ return { view: new DataView(buf.buffer), base: BigInt(ptr(buf)) };
135
140
  }
136
141
  return null;
137
142
  }
138
143
 
144
+ async function readWindows(pid: number): Promise<ThreadSnapshot | null> {
145
+ const info = await querySystemProcessInformation();
146
+ return info ? parseWindowsProcessInformation(info.view, pid) : null;
147
+ }
148
+
139
149
  const LINUX_STATES: Record<string, string> = {
140
150
  R: "running", S: "sleeping", D: "disk-wait", T: "stopped", t: "traced", Z: "zombie", X: "dead",
141
151
  I: "idle", W: "paging", P: "parked",
@@ -0,0 +1,161 @@
1
+ /**
2
+ * How much memory every process below this one holds, read from the OS.
3
+ *
4
+ * The monitor's own numbers cover one process, and on 26/10/2026 that was the smaller half:
5
+ * the server held 7 GB while its 17 Claude subprocesses held 11 GB between them, and nothing
6
+ * PPM logged could say so. The spawn tracker only sees direct children, and only the ones
7
+ * started after it was installed; the OS sees the whole tree, grandchildren included (an MCP
8
+ * server under a Claude subprocess, a test runner under a shell).
9
+ *
10
+ * Windows reads every process with one `NtQuerySystemInformation` call (the same one
11
+ * `process-thread-snapshot.ts` makes); Linux walks `/proc`. Other platforms answer null.
12
+ * Nothing here throws.
13
+ */
14
+ import { readdirSync, readFileSync } from "node:fs";
15
+ import { querySystemProcessInformation } from "./process-thread-snapshot.ts";
16
+
17
+ export interface ProcessMemoryEntry {
18
+ pid: number;
19
+ ppid: number;
20
+ /** Image name, lowercased, executable extension dropped. */
21
+ name: string;
22
+ privateMB: number;
23
+ /** When the process started, ms since the epoch on Windows and since boot on Linux: only compared, never shown. */
24
+ startedAt: number;
25
+ }
26
+
27
+ const MB = 1024 * 1024;
28
+
29
+ // SYSTEM_PROCESS_INFORMATION on 64-bit Windows.
30
+ const P_NEXT = 0x00;
31
+ const P_CREATE_TIME = 0x20;
32
+ const P_IMAGE_NAME_LENGTH = 0x38;
33
+ const P_IMAGE_NAME_BUFFER = 0x40;
34
+ const P_PID = 0x50;
35
+ const P_PARENT_PID = 0x58;
36
+ const P_PRIVATE_BYTES = 0xc8;
37
+ const P_SIZE = 0x100;
38
+ /** 100 ns ticks between 1601-01-01 and 1970-01-01. */
39
+ const EPOCH_TICKS = 116_444_736_000_000_000n;
40
+
41
+ export function processName(raw: string): string {
42
+ return raw.toLowerCase().replace(/\.(exe|cmd|bat|com)$/, "").slice(0, 40) || "unknown";
43
+ }
44
+
45
+ /** Every process in a `SystemProcessInformation` buffer. Exported for tests. */
46
+ export function parseWindowsProcessList(view: DataView, base: bigint): ProcessMemoryEntry[] {
47
+ const out: ProcessMemoryEntry[] = [];
48
+ let at = 0;
49
+ for (;;) {
50
+ if (at + P_SIZE > view.byteLength) break;
51
+ const pid = Number(view.getBigUint64(at + P_PID, true));
52
+ let name = pid === 0 ? "idle" : "system";
53
+ const nameBytes = view.getUint16(at + P_IMAGE_NAME_LENGTH, true);
54
+ const namePtr = view.getBigUint64(at + P_IMAGE_NAME_BUFFER, true);
55
+ const offset = Number(namePtr - base);
56
+ if (nameBytes > 0 && offset >= 0 && offset + nameBytes <= view.byteLength) {
57
+ name = new TextDecoder("utf-16le").decode(new Uint8Array(view.buffer, view.byteOffset + offset, nameBytes));
58
+ }
59
+ const created = view.getBigInt64(at + P_CREATE_TIME, true);
60
+ out.push({
61
+ pid,
62
+ ppid: Number(view.getBigUint64(at + P_PARENT_PID, true)),
63
+ name: processName(name),
64
+ privateMB: Math.round(Number(view.getBigUint64(at + P_PRIVATE_BYTES, true)) / MB),
65
+ startedAt: created > EPOCH_TICKS ? Number((created - EPOCH_TICKS) / 10_000n) : 0,
66
+ });
67
+ const next = view.getUint32(at + P_NEXT, true);
68
+ if (next === 0) break;
69
+ at += next;
70
+ }
71
+ return out;
72
+ }
73
+
74
+ /** `/proc/<pid>/stat` → ppid, name and start time (field 22, clock ticks since boot). Exported for tests. */
75
+ export function parseLinuxStatLine(text: string): { ppid: number; name: string; startedAt: number } | null {
76
+ const open = text.indexOf("(");
77
+ const close = text.lastIndexOf(")");
78
+ if (open === -1 || close === -1) return null;
79
+ const f = text.slice(close + 2).trim().split(/\s+/);
80
+ const ppid = Number(f[4 - 3]);
81
+ const startedAt = Number(f[22 - 3]);
82
+ if (!Number.isFinite(ppid)) return null;
83
+ return { ppid, name: processName(text.slice(open + 1, close)), startedAt: Number.isFinite(startedAt) ? startedAt : 0 };
84
+ }
85
+
86
+ function linuxPrivateMB(pid: number): number {
87
+ try {
88
+ const status = readFileSync(`/proc/${pid}/status`, "utf8");
89
+ const kb = (key: string) => Number(status.match(new RegExp(`^${key}:\\s+(\\d+)`, "m"))?.[1] ?? 0);
90
+ return Math.round((kb("RssAnon") + kb("VmSwap")) / 1024);
91
+ } catch {
92
+ return 0;
93
+ }
94
+ }
95
+
96
+ function readLinuxList(rootPid: number): ProcessMemoryEntry[] {
97
+ const all: ProcessMemoryEntry[] = [];
98
+ for (const entry of readdirSync("/proc")) {
99
+ const pid = Number(entry);
100
+ if (!Number.isInteger(pid)) continue;
101
+ try {
102
+ const stat = parseLinuxStatLine(readFileSync(`/proc/${pid}/stat`, "utf8"));
103
+ if (stat) all.push({ pid, ...stat, privateMB: 0 });
104
+ } catch { /* exited while walking */ }
105
+ }
106
+ // Memory only for the tree that is reported: one status read per descendant, not per process.
107
+ const tree = new Set(descendantsOf(rootPid, all).map((d) => d.pid));
108
+ for (const p of all) if (tree.has(p.pid) || p.pid === rootPid) p.privateMB = linuxPrivateMB(p.pid);
109
+ return all;
110
+ }
111
+
112
+ /** Every process the OS lists, or null when this platform cannot be read. */
113
+ export async function readProcessList(rootPid = process.pid, platform: NodeJS.Platform = process.platform): Promise<ProcessMemoryEntry[] | null> {
114
+ try {
115
+ if (platform === "win32") {
116
+ const info = await querySystemProcessInformation();
117
+ return info ? parseWindowsProcessList(info.view, info.base) : null;
118
+ }
119
+ if (platform === "linux") return readLinuxList(rootPid);
120
+ } catch { /* fall through */ }
121
+ return null;
122
+ }
123
+
124
+ export interface Descendant extends ProcessMemoryEntry {
125
+ depth: number;
126
+ /** The direct child of the root this process descends from (itself, at depth 1). */
127
+ branchPid: number;
128
+ }
129
+
130
+ /**
131
+ * Every process below `rootPid`. A process counts as a child only if it started after its
132
+ * parent: Windows reuses pids, so a ppid can name an unrelated process that took a dead
133
+ * parent's number, and without the check that process's whole tree would be claimed.
134
+ */
135
+ export function descendantsOf(rootPid: number, list: ProcessMemoryEntry[]): Descendant[] {
136
+ const byParent = new Map<number, ProcessMemoryEntry[]>();
137
+ const byPid = new Map<number, ProcessMemoryEntry>();
138
+ for (const p of list) {
139
+ byPid.set(p.pid, p);
140
+ if (p.pid === p.ppid) continue;
141
+ const siblings = byParent.get(p.ppid);
142
+ if (siblings) siblings.push(p); else byParent.set(p.ppid, [p]);
143
+ }
144
+ const out: Descendant[] = [];
145
+ const queue: Array<{ p: ProcessMemoryEntry; depth: number; branchPid: number }> = [];
146
+ const root = byPid.get(rootPid);
147
+ for (const c of byParent.get(rootPid) ?? []) {
148
+ if (!root || c.startedAt >= root.startedAt) queue.push({ p: c, depth: 1, branchPid: c.pid });
149
+ }
150
+ const seen = new Set<number>([rootPid]);
151
+ while (queue.length) {
152
+ const { p, depth, branchPid } = queue.shift()!;
153
+ if (seen.has(p.pid)) continue;
154
+ seen.add(p.pid);
155
+ out.push({ ...p, depth, branchPid });
156
+ for (const c of byParent.get(p.pid) ?? []) {
157
+ if (c.startedAt >= p.startedAt) queue.push({ p: c, depth: depth + 1, branchPid });
158
+ }
159
+ }
160
+ return out;
161
+ }
@@ -5,6 +5,7 @@
5
5
  * compaction scan — and having the second import it from the first would make the
6
6
  * two files a cycle.
7
7
  */
8
+ import { readFileChunks } from "./file-chunks.ts";
8
9
 
9
10
  /**
10
11
  * Yield a file's lines without holding the file in memory.
@@ -19,47 +20,35 @@
19
20
  *
20
21
  * `toByte` ends the read there, exclusive. It is meant to be a line start (the
21
22
  * record that begins there is not read) or omitted for the end of the file —
22
- * `compactSegmentWindow` in `compact-segment.ts` is what computes one. It is
23
- * counted here rather than handed to `slice()` as an end, because in Bun 1.3.11
24
- * a sliced file's stream that has to stop well short of EOF never finishes:
25
- * on a 4MB file `slice(0, 259).stream()` hung, `slice(0, 4194303)` did not.
23
+ * `compactSegmentWindow` in `compact-segment.ts` is what computes one.
24
+ *
25
+ * The bytes come from `readFileChunks`, not `Bun.file().stream()`: that stream
26
+ * keeps about the file's size in native memory per full read and never returns
27
+ * it (see `file-chunks.ts`), and its sliced form never finishes when it has to
28
+ * stop well short of EOF (Bun 1.3.11: on a 4MB file `slice(0, 259).stream()`
29
+ * hung).
26
30
  */
27
31
  export async function* readLines(filePath: string, fromByte = 0, toByte?: number): AsyncGenerator<string> {
28
32
  const decoder = new TextDecoder();
29
- // Explicit reader rather than `for await` over the stream: the DOM lib's
30
- // ReadableStream is not typed as async-iterable, and the `finally` is what
31
- // releases it when a caller breaks early on `beforeUuid`.
32
33
  // One byte *before* the offset, so the first line the split produces is
33
34
  // always the tail of the record the offset fell inside — the empty string
34
35
  // when it fell exactly on a newline. That makes the drop below unconditional
35
36
  // instead of a boundary case, and a boundary case here silently costs a whole
36
37
  // record rather than a fragment.
37
- const file = fromByte > 0 ? Bun.file(filePath).slice(fromByte - 1) : Bun.file(filePath);
38
- const reader = file.stream().getReader();
39
38
  // A fragment is not always unparseable — the tail of one record is sometimes
40
39
  // a complete smaller document — so it has to go by position rather than be
41
40
  // left for `JSON.parse` to reject, or it enters as an invented message.
42
41
  let dropPartial = fromByte > 0;
43
- let remaining = toByte === undefined ? Infinity : toByte - Math.max(0, fromByte - 1);
44
42
  let buffered = "";
45
- try {
46
- while (remaining > 0) {
47
- const { done, value } = await reader.read();
48
- if (done) break;
49
- const chunk = value.byteLength > remaining ? value.subarray(0, remaining) : value;
50
- remaining -= chunk.byteLength;
51
- buffered += decoder.decode(chunk, { stream: true });
52
- const lines = buffered.split("\n");
53
- buffered = lines.pop() ?? "";
54
- for (const line of lines) {
55
- if (dropPartial) { dropPartial = false; continue; }
56
- yield line;
57
- }
43
+ // A caller that stops early (`beforeUuid`) ends this loop, which closes the file.
44
+ for await (const chunk of readFileChunks(filePath, { fromByte: Math.max(0, fromByte - 1), toByte })) {
45
+ buffered += decoder.decode(chunk, { stream: true });
46
+ const lines = buffered.split("\n");
47
+ buffered = lines.pop() ?? "";
48
+ for (const line of lines) {
49
+ if (dropPartial) { dropPartial = false; continue; }
50
+ yield line;
58
51
  }
59
- } finally {
60
- // `cancel`, not only `releaseLock`: a stream let go of before its end keeps
61
- // the file open — 2000 early stops left 2000 descriptors behind.
62
- await reader.cancel().catch(() => {});
63
52
  }
64
53
  buffered += decoder.decode();
65
54
  if (buffered && !dropPartial) yield buffered;