@hienlh/ppm 0.23.20 → 0.23.22

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 (466) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/CLAUDE.md +3 -2
  3. package/assets/skills/ppm/SKILL.md +1 -1
  4. package/assets/skills/ppm/references/http-api.md +1 -1
  5. package/dist/web/assets/{accounts-settings-section-BTga1zwH.js → accounts-settings-section-CiNmIj02.js} +1 -1
  6. package/dist/web/assets/accounts-settings-section-CiNmIj02.js.br +0 -0
  7. package/dist/web/assets/accounts-settings-section-CiNmIj02.js.gz +0 -0
  8. package/dist/web/assets/{agent-session-sheet-Q3kasbrZ.js → agent-session-sheet-DRR36PDA.js} +2 -2
  9. package/dist/web/assets/agent-session-sheet-DRR36PDA.js.br +0 -0
  10. package/dist/web/assets/agent-session-sheet-DRR36PDA.js.gz +0 -0
  11. package/dist/web/assets/{agent-session-window-content-UNuEprIK.js → agent-session-window-content-DMhy16BM.js} +1 -1
  12. package/dist/web/assets/agent-session-window-content-DMhy16BM.js.br +0 -0
  13. package/dist/web/assets/agent-session-window-content-DMhy16BM.js.gz +0 -0
  14. package/dist/web/assets/{ai-resource-editor-BEwA6Do0.js → ai-resource-editor-BG8boPw7.js} +1 -1
  15. package/dist/web/assets/ai-resource-editor-BG8boPw7.js.br +0 -0
  16. package/dist/web/assets/ai-resource-editor-BG8boPw7.js.gz +0 -0
  17. package/dist/web/assets/{ai-settings-section-DVKJPM0n.js → ai-settings-section-DMGM1hYx.js} +1 -1
  18. package/dist/web/assets/ai-settings-section-DMGM1hYx.js.br +0 -0
  19. package/dist/web/assets/ai-settings-section-DMGM1hYx.js.gz +0 -0
  20. package/dist/web/assets/{ai-settings-section-DlfY5lFT.js → ai-settings-section-De5dYWlC.js} +1 -1
  21. package/dist/web/assets/{android-tab-BNa6R9fV.js → android-tab-lHDxD93G.js} +1 -1
  22. package/dist/web/assets/android-tab-lHDxD93G.js.br +0 -0
  23. package/dist/web/assets/android-tab-lHDxD93G.js.gz +0 -0
  24. package/dist/web/assets/{appearance-settings-section-CjWFwWNn.js → appearance-settings-section-DO-ZyoMj.js} +1 -1
  25. package/dist/web/assets/appearance-settings-section-DO-ZyoMj.js.br +0 -0
  26. package/dist/web/assets/appearance-settings-section-DO-ZyoMj.js.gz +0 -0
  27. package/dist/web/assets/{apps-panel-D4xjeMN9.js → apps-panel-CH-0CHYY.js} +1 -1
  28. package/dist/web/assets/apps-panel-CH-0CHYY.js.br +0 -0
  29. package/dist/web/assets/apps-panel-CH-0CHYY.js.gz +0 -0
  30. package/dist/web/assets/{branch-review-tab-BCW5294d.js → branch-review-tab-CdGtvekz.js} +1 -1
  31. package/dist/web/assets/branch-review-tab-CdGtvekz.js.br +0 -0
  32. package/dist/web/assets/branch-review-tab-CdGtvekz.js.gz +0 -0
  33. package/dist/web/assets/{chat-tab-BG0M1D3d.js → chat-tab-DMqWnFIo.js} +1 -1
  34. package/dist/web/assets/chat-tab-DMqWnFIo.js.br +0 -0
  35. package/dist/web/assets/chat-tab-DMqWnFIo.js.gz +0 -0
  36. package/dist/web/assets/{chat-tab-DsW93LTL.js → chat-tab-DzWpwPFb.js} +4 -4
  37. package/dist/web/assets/chat-tab-DzWpwPFb.js.br +0 -0
  38. package/dist/web/assets/chat-tab-DzWpwPFb.js.gz +0 -0
  39. package/dist/web/assets/{code-editor-AxLIpK-b.js → code-editor-BCv8ONNS.js} +4 -4
  40. package/dist/web/assets/code-editor-BCv8ONNS.js.br +0 -0
  41. package/dist/web/assets/code-editor-BCv8ONNS.js.gz +0 -0
  42. package/dist/web/assets/{codex-reset-credit-button-BnRmkBZu.js → codex-reset-credit-button-CGMbIHak.js} +1 -1
  43. package/dist/web/assets/codex-reset-credit-button-CGMbIHak.js.br +0 -0
  44. package/dist/web/assets/codex-reset-credit-button-CGMbIHak.js.gz +0 -0
  45. package/dist/web/assets/{conflict-editor-BwuNUPAK.js → conflict-editor-3WmzqBlW.js} +1 -1
  46. package/dist/web/assets/conflict-editor-3WmzqBlW.js.br +0 -0
  47. package/dist/web/assets/conflict-editor-3WmzqBlW.js.gz +0 -0
  48. package/dist/web/assets/{connection-form-tab-CM2o80eB.js → connection-form-tab-BoWucvAe.js} +1 -1
  49. package/dist/web/assets/connection-form-tab-BoWucvAe.js.br +0 -0
  50. package/dist/web/assets/connection-form-tab-BoWucvAe.js.gz +0 -0
  51. package/dist/web/assets/{csv-preview-C7ErHm4U.js → csv-preview-BCgGsy7F.js} +1 -1
  52. package/dist/web/assets/csv-preview-BCgGsy7F.js.br +0 -0
  53. package/dist/web/assets/csv-preview-BCgGsy7F.js.gz +0 -0
  54. package/dist/web/assets/{db-tab-parts-C8jbkfxO.js → db-tab-parts-o6QMHBbA.js} +1 -1
  55. package/dist/web/assets/db-tab-parts-o6QMHBbA.js.br +0 -0
  56. package/dist/web/assets/db-tab-parts-o6QMHBbA.js.gz +0 -0
  57. package/dist/web/assets/{design-settings-section-BcGEY4Zf.js → design-settings-section-seo6SdSk.js} +1 -1
  58. package/dist/web/assets/design-settings-section-seo6SdSk.js.br +0 -0
  59. package/dist/web/assets/design-settings-section-seo6SdSk.js.gz +0 -0
  60. package/dist/web/assets/{design-tab-BGPYNFvy.js → design-tab-B-f_b2LM.js} +1 -1
  61. package/dist/web/assets/design-tab-B-f_b2LM.js.br +0 -0
  62. package/dist/web/assets/design-tab-B-f_b2LM.js.gz +0 -0
  63. package/dist/web/assets/{designs-sidebar-panel-BgMj7xRm.js → designs-sidebar-panel-CVT_TQP7.js} +1 -1
  64. package/dist/web/assets/designs-sidebar-panel-CVT_TQP7.js.br +0 -0
  65. package/dist/web/assets/designs-sidebar-panel-CVT_TQP7.js.gz +0 -0
  66. package/dist/web/assets/{diff-viewer-BKJkesHk.js → diff-viewer-y9uBp_8u.js} +1 -1
  67. package/dist/web/assets/diff-viewer-y9uBp_8u.js.br +0 -0
  68. package/dist/web/assets/diff-viewer-y9uBp_8u.js.gz +0 -0
  69. package/dist/web/assets/{docx-preview-DD5oD_nL.js → docx-preview-Dn_x3Inm.js} +1 -1
  70. package/dist/web/assets/docx-preview-Dn_x3Inm.js.br +0 -0
  71. package/dist/web/assets/docx-preview-Dn_x3Inm.js.gz +0 -0
  72. package/dist/web/assets/{editor-dialog--07lKJxl.js → editor-dialog-BwNXSajD.js} +1 -1
  73. package/dist/web/assets/editor-dialog-BwNXSajD.js.br +0 -0
  74. package/dist/web/assets/editor-dialog-BwNXSajD.js.gz +0 -0
  75. package/dist/web/assets/{editor-language-service-51xjO5d3.js → editor-language-service-XA_TnPLN.js} +1 -1
  76. package/dist/web/assets/editor-language-service-XA_TnPLN.js.br +0 -0
  77. package/dist/web/assets/editor-language-service-XA_TnPLN.js.gz +0 -0
  78. package/dist/web/assets/{explorer-body-BXK5SjJQ.js → explorer-body-DukR7zM9.js} +1 -1
  79. package/dist/web/assets/explorer-body-DukR7zM9.js.br +0 -0
  80. package/dist/web/assets/explorer-body-DukR7zM9.js.gz +0 -0
  81. package/dist/web/assets/{explorer-window-content-DKXcCmuX.js → explorer-window-content-BjTZ44S3.js} +1 -1
  82. package/dist/web/assets/explorer-window-content-BjTZ44S3.js.br +0 -0
  83. package/dist/web/assets/explorer-window-content-BjTZ44S3.js.gz +0 -0
  84. package/dist/web/assets/{extension-manager-section-CTpQP6te.js → extension-manager-section-C4WD1BPQ.js} +1 -1
  85. package/dist/web/assets/extension-manager-section-C4WD1BPQ.js.br +0 -0
  86. package/dist/web/assets/extension-manager-section-C4WD1BPQ.js.gz +0 -0
  87. package/dist/web/assets/{extension-webview-COT-yLrM.js → extension-webview-CV2ujflc.js} +1 -1
  88. package/dist/web/assets/extension-webview-CV2ujflc.js.br +0 -0
  89. package/dist/web/assets/extension-webview-CV2ujflc.js.gz +0 -0
  90. package/dist/web/assets/{files-settings-section-CgVR0fqi.js → files-settings-section-hS5ixQ8o.js} +1 -1
  91. package/dist/web/assets/files-settings-section-hS5ixQ8o.js.br +0 -0
  92. package/dist/web/assets/files-settings-section-hS5ixQ8o.js.gz +0 -0
  93. package/dist/web/assets/{general-settings-section-BEMIbE4Y.js → general-settings-section-BnKYY0AA.js} +1 -1
  94. package/dist/web/assets/general-settings-section-BnKYY0AA.js.br +0 -0
  95. package/dist/web/assets/general-settings-section-BnKYY0AA.js.gz +0 -0
  96. package/dist/web/assets/{git-log-panel-Cp2174iI.js → git-log-panel-JiWfR5xv.js} +1 -1
  97. package/dist/web/assets/git-log-panel-JiWfR5xv.js.br +0 -0
  98. package/dist/web/assets/git-log-panel-JiWfR5xv.js.gz +0 -0
  99. package/dist/web/assets/{git-review-tab-CpMJ0qDi.js → git-review-tab-DgvcY__Q.js} +1 -1
  100. package/dist/web/assets/git-review-tab-DgvcY__Q.js.br +0 -0
  101. package/dist/web/assets/git-review-tab-DgvcY__Q.js.gz +0 -0
  102. package/dist/web/assets/{glide-data-grid-BAhwWUhO.js → glide-data-grid-r1MKxDjz.js} +1 -1
  103. package/dist/web/assets/glide-data-grid-r1MKxDjz.js.br +0 -0
  104. package/dist/web/assets/glide-data-grid-r1MKxDjz.js.gz +0 -0
  105. package/dist/web/assets/{grid-save-dialog-DmJhaWOl.js → grid-save-dialog-Fa0yA-Ep.js} +1 -1
  106. package/dist/web/assets/grid-save-dialog-Fa0yA-Ep.js.br +0 -0
  107. package/dist/web/assets/grid-save-dialog-Fa0yA-Ep.js.gz +0 -0
  108. package/dist/web/assets/{group-chat-tab-Bq7-Yegf.js → group-chat-tab-B2Q8yqSX.js} +1 -1
  109. package/dist/web/assets/group-chat-tab-B2Q8yqSX.js.br +0 -0
  110. package/dist/web/assets/group-chat-tab-B2Q8yqSX.js.gz +0 -0
  111. package/dist/web/assets/{impexp-tab-COx3Cvs7.js → impexp-tab-B8Ht4l32.js} +1 -1
  112. package/dist/web/assets/impexp-tab-B8Ht4l32.js.br +0 -0
  113. package/dist/web/assets/impexp-tab-B8Ht4l32.js.gz +0 -0
  114. package/dist/web/assets/{index-BGk8EFQb.js → index-BtAn_krv.js} +5 -5
  115. package/dist/web/assets/index-BtAn_krv.js.br +0 -0
  116. package/dist/web/assets/index-BtAn_krv.js.gz +0 -0
  117. package/dist/web/assets/{jira-watcher-section-BEPbsi62.js → jira-watcher-section--ibKZfBs.js} +1 -1
  118. package/dist/web/assets/{keybindings-store-DzcSIJUF.js → keybindings-store-Cwxl4tHv.js} +1 -1
  119. package/dist/web/assets/{keyboard-shortcuts-section-Bqg7AZiZ.js → keyboard-shortcuts-section-aoiLpWwf.js} +1 -1
  120. package/dist/web/assets/keyboard-shortcuts-section-aoiLpWwf.js.br +0 -0
  121. package/dist/web/assets/keyboard-shortcuts-section-aoiLpWwf.js.gz +0 -0
  122. package/dist/web/assets/{language-servers-section-CJ47DSIA.js → language-servers-section-BkGAezo6.js} +2 -2
  123. package/dist/web/assets/language-servers-section-BkGAezo6.js.br +0 -0
  124. package/dist/web/assets/language-servers-section-BkGAezo6.js.gz +0 -0
  125. package/dist/web/assets/{logs-app-BK-n-y_K.js → logs-app-DYgPtmjm.js} +1 -1
  126. package/dist/web/assets/logs-app-DYgPtmjm.js.br +0 -0
  127. package/dist/web/assets/logs-app-DYgPtmjm.js.gz +0 -0
  128. package/dist/web/assets/{logs-tab-B84EZJ1b.js → logs-tab-BbDosK63.js} +1 -1
  129. package/dist/web/assets/{logs-window-content-B1uN_3pN.js → logs-window-content-CEXAyv-f.js} +1 -1
  130. package/dist/web/assets/{lsp-client-DHSaAyJW.js → lsp-client-CIcphNmU.js} +1 -1
  131. package/dist/web/assets/lsp-client-CIcphNmU.js.br +0 -0
  132. package/dist/web/assets/lsp-client-CIcphNmU.js.gz +0 -0
  133. package/dist/web/assets/{markdown-renderer-BjDQUqT-.js → markdown-renderer-BNzQeoAO.js} +1 -1
  134. package/dist/web/assets/{markdown-renderer-CpxdNG0f.js → markdown-renderer-dk1w3goH.js} +1 -1
  135. package/dist/web/assets/markdown-renderer-dk1w3goH.js.br +0 -0
  136. package/dist/web/assets/markdown-renderer-dk1w3goH.js.gz +0 -0
  137. package/dist/web/assets/{mobile-explorer-sheet-BBwXGmXA.js → mobile-explorer-sheet-D4lPZQZc.js} +1 -1
  138. package/dist/web/assets/mobile-explorer-sheet-D4lPZQZc.js.br +0 -0
  139. package/dist/web/assets/mobile-explorer-sheet-D4lPZQZc.js.gz +0 -0
  140. package/dist/web/assets/{new-design-dialog-B_ZOvGhR.js → new-design-dialog-CIq7xspW.js} +1 -1
  141. package/dist/web/assets/new-design-dialog-CIq7xspW.js.br +0 -0
  142. package/dist/web/assets/new-design-dialog-CIq7xspW.js.gz +0 -0
  143. package/dist/web/assets/{notification-store-HuMqfadD.js → notification-store-Rv36noYX.js} +1 -1
  144. package/dist/web/assets/notifications-settings-section-CoWMtXCC.js +1 -0
  145. package/dist/web/assets/notifications-settings-section-CoWMtXCC.js.br +0 -0
  146. package/dist/web/assets/notifications-settings-section-CoWMtXCC.js.gz +0 -0
  147. package/dist/web/assets/{ppmbot-settings-section-JDvyI-gZ.js → ppmbot-settings-section-Ct0UwA6T.js} +1 -1
  148. package/dist/web/assets/ppmbot-settings-section-Ct0UwA6T.js.br +0 -0
  149. package/dist/web/assets/ppmbot-settings-section-Ct0UwA6T.js.gz +0 -0
  150. package/dist/web/assets/{problems-panel-Bsv_L6T2.js → problems-panel-D-HT-0PE.js} +1 -1
  151. package/dist/web/assets/problems-panel-D-HT-0PE.js.br +0 -0
  152. package/dist/web/assets/problems-panel-D-HT-0PE.js.gz +0 -0
  153. package/dist/web/assets/{process-table-3kZyWOJx.js → process-table-CXN0s4Pq.js} +1 -1
  154. package/dist/web/assets/process-table-CXN0s4Pq.js.br +0 -0
  155. package/dist/web/assets/process-table-CXN0s4Pq.js.gz +0 -0
  156. package/dist/web/assets/{proxy-settings-section-CYcmIp7W.js → proxy-settings-section-e6WWzP8n.js} +1 -1
  157. package/dist/web/assets/proxy-settings-section-e6WWzP8n.js.br +0 -0
  158. package/dist/web/assets/proxy-settings-section-e6WWzP8n.js.gz +0 -0
  159. package/dist/web/assets/{query-tab-CjrWnQUl.js → query-tab-C7C67t8F.js} +1 -1
  160. package/dist/web/assets/query-tab-C7C67t8F.js.br +0 -0
  161. package/dist/web/assets/query-tab-C7C67t8F.js.gz +0 -0
  162. package/dist/web/assets/{remote-access-settings-section-iQkakiVx.js → remote-access-settings-section-gGoATCF9.js} +1 -1
  163. package/dist/web/assets/remote-access-settings-section-gGoATCF9.js.br +0 -0
  164. package/dist/web/assets/remote-access-settings-section-gGoATCF9.js.gz +0 -0
  165. package/dist/web/assets/{remote-desktop-clipboard-notice-CFLnb7ML.js → remote-desktop-clipboard-notice-D5KjvMZx.js} +1 -1
  166. package/dist/web/assets/remote-desktop-clipboard-notice-D5KjvMZx.js.br +0 -0
  167. package/dist/web/assets/remote-desktop-clipboard-notice-D5KjvMZx.js.gz +0 -0
  168. package/dist/web/assets/{remote-desktop-mobile-sheet-C_-_-Nkz.js → remote-desktop-mobile-sheet-CY_DsGXZ.js} +2 -2
  169. package/dist/web/assets/remote-desktop-mobile-sheet-CY_DsGXZ.js.br +0 -0
  170. package/dist/web/assets/remote-desktop-mobile-sheet-CY_DsGXZ.js.gz +0 -0
  171. package/dist/web/assets/{remote-desktop-mobile-view-BnHSrind.js → remote-desktop-mobile-view-DdpI-RF8.js} +1 -1
  172. package/dist/web/assets/remote-desktop-mobile-view-DdpI-RF8.js.br +0 -0
  173. package/dist/web/assets/remote-desktop-mobile-view-DdpI-RF8.js.gz +0 -0
  174. package/dist/web/assets/{remote-desktop-readiness-gate-BGHJ7Sv6.js → remote-desktop-readiness-gate-WIyCCAv5.js} +1 -1
  175. package/dist/web/assets/remote-desktop-readiness-gate-WIyCCAv5.js.br +0 -0
  176. package/dist/web/assets/remote-desktop-readiness-gate-WIyCCAv5.js.gz +0 -0
  177. package/dist/web/assets/{remote-desktop-window-content-BBm1YwM8.js → remote-desktop-window-content-BMORZLZt.js} +1 -1
  178. package/dist/web/assets/remote-desktop-window-content-BMORZLZt.js.br +0 -0
  179. package/dist/web/assets/remote-desktop-window-content-BMORZLZt.js.gz +0 -0
  180. package/dist/web/assets/{rename-object-dialog-BLnF9xyn.js → rename-object-dialog-pIgWe_AL.js} +1 -1
  181. package/dist/web/assets/rename-object-dialog-pIgWe_AL.js.br +0 -0
  182. package/dist/web/assets/rename-object-dialog-pIgWe_AL.js.gz +0 -0
  183. package/dist/web/assets/{review-code-DG7sCIzr.js → review-code-oTNcTYlI.js} +1 -1
  184. package/dist/web/assets/review-code-oTNcTYlI.js.br +0 -0
  185. package/dist/web/assets/review-code-oTNcTYlI.js.gz +0 -0
  186. package/dist/web/assets/{save-as-dialog-KW7cErWh.js → save-as-dialog-CnPLGTaL.js} +1 -1
  187. package/dist/web/assets/save-as-dialog-CnPLGTaL.js.br +0 -0
  188. package/dist/web/assets/save-as-dialog-CnPLGTaL.js.gz +0 -0
  189. package/dist/web/assets/{schedules-settings-section-B4AV4ORv.js → schedules-settings-section-CREqHG_i.js} +1 -1
  190. package/dist/web/assets/schedules-settings-section-CREqHG_i.js.br +0 -0
  191. package/dist/web/assets/schedules-settings-section-CREqHG_i.js.gz +0 -0
  192. package/dist/web/assets/{session-review-tab-C5nPyBzo.js → session-review-tab-nFVJYYJH.js} +1 -1
  193. package/dist/web/assets/session-review-tab-nFVJYYJH.js.br +0 -0
  194. package/dist/web/assets/session-review-tab-nFVJYYJH.js.gz +0 -0
  195. package/dist/web/assets/{settings-body-xWSmRXUD.js → settings-body-DNzH0wOQ.js} +1 -1
  196. package/dist/web/assets/settings-body-DNzH0wOQ.js.br +0 -0
  197. package/dist/web/assets/settings-body-DNzH0wOQ.js.gz +0 -0
  198. package/dist/web/assets/{settings-rows-ChKBHk07.js → settings-rows-D02_4sDY.js} +1 -1
  199. package/dist/web/assets/settings-rows-D02_4sDY.js.br +0 -0
  200. package/dist/web/assets/settings-rows-D02_4sDY.js.gz +0 -0
  201. package/dist/web/assets/settings-section-content-BYW_9mHv.js +1 -0
  202. package/dist/web/assets/{settings-section-content-DdVdyPVX.js → settings-section-content-DWopMfmq.js} +2 -2
  203. package/dist/web/assets/settings-section-content-DWopMfmq.js.br +0 -0
  204. package/dist/web/assets/settings-section-content-DWopMfmq.js.gz +0 -0
  205. package/dist/web/assets/{settings-tab-CzaBBNoj.js → settings-tab-Cg0v5kO7.js} +1 -1
  206. package/dist/web/assets/{settings-window-content-DAvWSu_O.js → settings-window-content-CqhwxJJn.js} +1 -1
  207. package/dist/web/assets/{slash-command-picker-Cn2Fdz2p.js → slash-command-picker-Bcizxvbh.js} +1 -1
  208. package/dist/web/assets/slash-command-picker-Bcizxvbh.js.br +0 -0
  209. package/dist/web/assets/slash-command-picker-Bcizxvbh.js.gz +0 -0
  210. package/dist/web/assets/{sql-object-tab-_mPRCvD9.js → sql-object-tab-b1Z2zVBm.js} +1 -1
  211. package/dist/web/assets/sql-object-tab-b1Z2zVBm.js.br +0 -0
  212. package/dist/web/assets/sql-object-tab-b1Z2zVBm.js.gz +0 -0
  213. package/dist/web/assets/{sqlite-viewer-B8VZt-mA.js → sqlite-viewer-DhPv28tq.js} +1 -1
  214. package/dist/web/assets/sqlite-viewer-DhPv28tq.js.br +0 -0
  215. package/dist/web/assets/sqlite-viewer-DhPv28tq.js.gz +0 -0
  216. package/dist/web/assets/{structure-save-dialog-Lcst7kAl.js → structure-save-dialog-sGUpqs8D.js} +1 -1
  217. package/dist/web/assets/structure-save-dialog-sGUpqs8D.js.br +0 -0
  218. package/dist/web/assets/structure-save-dialog-sGUpqs8D.js.gz +0 -0
  219. package/dist/web/assets/{structure-tab-ljJCzxUe.js → structure-tab-Bo2OmDOc.js} +1 -1
  220. package/dist/web/assets/structure-tab-Bo2OmDOc.js.br +0 -0
  221. package/dist/web/assets/structure-tab-Bo2OmDOc.js.gz +0 -0
  222. package/dist/web/assets/{system-monitor-body-CTi5xUZz.js → system-monitor-body-C475Et9u.js} +2 -2
  223. package/dist/web/assets/system-monitor-body-C475Et9u.js.br +0 -0
  224. package/dist/web/assets/system-monitor-body-C475Et9u.js.gz +0 -0
  225. package/dist/web/assets/{system-monitor-tab-DeL2YoXO.js → system-monitor-tab-Cm52yoW8.js} +1 -1
  226. package/dist/web/assets/{system-monitor-window-content-DK-rQIZh.js → system-monitor-window-content-YYsGHxZF.js} +1 -1
  227. package/dist/web/assets/{tab-host-window-content-CG0vPTc_.js → tab-host-window-content-BKI6QoDa.js} +1 -1
  228. package/dist/web/assets/tab-host-window-content-BKI6QoDa.js.br +0 -0
  229. package/dist/web/assets/tab-host-window-content-BKI6QoDa.js.gz +0 -0
  230. package/dist/web/assets/{table-tab-CTZ3nCWV.js → table-tab-D-nKWMJR.js} +1 -1
  231. package/dist/web/assets/table-tab-D-nKWMJR.js.br +0 -0
  232. package/dist/web/assets/table-tab-D-nKWMJR.js.gz +0 -0
  233. package/dist/web/assets/{telegram-bot-token-field-DF7zq7_h.js → telegram-bot-token-field-BeNS2X2k.js} +1 -1
  234. package/dist/web/assets/telegram-bot-token-field-BeNS2X2k.js.br +0 -0
  235. package/dist/web/assets/telegram-bot-token-field-BeNS2X2k.js.gz +0 -0
  236. package/dist/web/assets/{terminal-tab-B_jECWTH.js → terminal-tab-BbP753Ab.js} +1 -1
  237. package/dist/web/assets/terminal-tab-BbP753Ab.js.br +0 -0
  238. package/dist/web/assets/terminal-tab-BbP753Ab.js.gz +0 -0
  239. package/dist/web/assets/{tool-cards-CsD_VXcG.js → tool-cards-Dh8Ekn2W.js} +2 -2
  240. package/dist/web/assets/tool-cards-Dh8Ekn2W.js.br +0 -0
  241. package/dist/web/assets/tool-cards-Dh8Ekn2W.js.gz +0 -0
  242. package/dist/web/assets/{tools-settings-section-DbBr0Hnr.js → tools-settings-section-DGZRIULE.js} +1 -1
  243. package/dist/web/assets/tools-settings-section-DGZRIULE.js.br +0 -0
  244. package/dist/web/assets/tools-settings-section-DGZRIULE.js.gz +0 -0
  245. package/dist/web/assets/{use-inline-blame-BA2mrMxT.js → use-inline-blame-BxwM-Wkj.js} +1 -1
  246. package/dist/web/assets/use-inline-blame-BxwM-Wkj.js.br +0 -0
  247. package/dist/web/assets/use-inline-blame-BxwM-Wkj.js.gz +0 -0
  248. package/dist/web/assets/{use-lsp-ChbrzwvS.js → use-lsp-BqvwRKmM.js} +1 -1
  249. package/dist/web/assets/use-lsp-BqvwRKmM.js.br +0 -0
  250. package/dist/web/assets/use-lsp-BqvwRKmM.js.gz +0 -0
  251. package/dist/web/assets/{use-monaco-theme-C3_wfz5J.js → use-monaco-theme-DU0EpDHH.js} +1 -1
  252. package/dist/web/assets/{use-process-kill-C-YetnGd.js → use-process-kill-CVN_c4KF.js} +1 -1
  253. package/dist/web/assets/use-process-kill-CVN_c4KF.js.br +0 -0
  254. package/dist/web/assets/use-process-kill-CVN_c4KF.js.gz +0 -0
  255. package/dist/web/assets/{use-sql-schema-info-pKwLD9wk.js → use-sql-schema-info-C0wBmR6-.js} +1 -1
  256. package/dist/web/assets/use-sql-schema-info-C0wBmR6-.js.br +0 -0
  257. package/dist/web/assets/use-sql-schema-info-C0wBmR6-.js.gz +0 -0
  258. package/dist/web/assets/{use-websocket-BFnUq0-x.js → use-websocket-K0OUXbbY.js} +1 -1
  259. package/dist/web/assets/{web-preview-tab-DGLPuZYM.js → web-preview-tab-DOgLv8Gt.js} +1 -1
  260. package/dist/web/assets/web-preview-tab-DOgLv8Gt.js.br +0 -0
  261. package/dist/web/assets/web-preview-tab-DOgLv8Gt.js.gz +0 -0
  262. package/dist/web/index.html +1 -1
  263. package/dist/web/index.html.br +0 -0
  264. package/dist/web/index.html.gz +0 -0
  265. package/dist/web/sw.js +1 -1
  266. package/dist/web/sw.js.br +0 -0
  267. package/dist/web/sw.js.gz +0 -0
  268. package/docs/deployment-guide.md +39 -0
  269. package/package.json +1 -1
  270. package/scripts/mem-diag-report.ts +31 -0
  271. package/src/providers/registry.ts +10 -1
  272. package/src/server/index.ts +7 -6
  273. package/src/server/middleware/access-log.ts +3 -0
  274. package/src/server/middleware/cors-headers.ts +44 -0
  275. package/src/server/routes/chat.ts +4 -1
  276. package/src/server/routes/database-impexp.ts +1 -1
  277. package/src/server/routes/design-preview.ts +2 -2
  278. package/src/server/ws/chat.ts +3 -0
  279. package/src/services/compact-segment.ts +14 -19
  280. package/src/services/database/grid-export-tickets.ts +6 -2
  281. package/src/services/database/impexp/readers/file-text.ts +6 -3
  282. package/src/services/edge-forwarder.ts +12 -0
  283. package/src/services/file-chunks.ts +39 -0
  284. package/src/services/file-lines.ts +10 -17
  285. package/src/services/lsp/lsp-manager.ts +3 -0
  286. package/src/services/memory-diagnostics-native.ts +110 -0
  287. package/src/services/memory-diagnostics-report.ts +200 -0
  288. package/src/services/memory-diagnostics-requests.ts +95 -0
  289. package/src/services/memory-diagnostics-spawns.ts +242 -0
  290. package/src/services/memory-diagnostics.ts +121 -23
  291. package/src/services/memory-watchdog.ts +185 -0
  292. package/src/services/nested-subagent-spy.ts +3 -0
  293. package/src/services/process-thread-snapshot.ts +12 -2
  294. package/src/services/process-tree-memory.ts +161 -0
  295. package/src/services/read-lines.ts +16 -27
  296. package/src/services/supervisor.ts +1 -0
  297. package/src/services/terminal.service.ts +8 -0
  298. package/src/services/transcript-images-file.ts +7 -11
  299. package/src/web/components/chat/question-card.tsx +10 -4
  300. package/src/web/components/settings/notifications-settings-section.tsx +14 -1
  301. package/src/web/lib/web-push-client.ts +35 -5
  302. package/src/web/lib/web-push-support.ts +57 -4
  303. package/dist/web/assets/accounts-settings-section-BTga1zwH.js.br +0 -0
  304. package/dist/web/assets/accounts-settings-section-BTga1zwH.js.gz +0 -0
  305. package/dist/web/assets/agent-session-sheet-Q3kasbrZ.js.br +0 -0
  306. package/dist/web/assets/agent-session-sheet-Q3kasbrZ.js.gz +0 -0
  307. package/dist/web/assets/agent-session-window-content-UNuEprIK.js.br +0 -0
  308. package/dist/web/assets/agent-session-window-content-UNuEprIK.js.gz +0 -0
  309. package/dist/web/assets/ai-resource-editor-BEwA6Do0.js.br +0 -0
  310. package/dist/web/assets/ai-resource-editor-BEwA6Do0.js.gz +0 -0
  311. package/dist/web/assets/ai-settings-section-DVKJPM0n.js.br +0 -0
  312. package/dist/web/assets/ai-settings-section-DVKJPM0n.js.gz +0 -0
  313. package/dist/web/assets/android-tab-BNa6R9fV.js.br +0 -0
  314. package/dist/web/assets/android-tab-BNa6R9fV.js.gz +0 -0
  315. package/dist/web/assets/appearance-settings-section-CjWFwWNn.js.br +0 -0
  316. package/dist/web/assets/appearance-settings-section-CjWFwWNn.js.gz +0 -0
  317. package/dist/web/assets/apps-panel-D4xjeMN9.js.br +0 -0
  318. package/dist/web/assets/apps-panel-D4xjeMN9.js.gz +0 -0
  319. package/dist/web/assets/branch-review-tab-BCW5294d.js.br +0 -0
  320. package/dist/web/assets/branch-review-tab-BCW5294d.js.gz +0 -0
  321. package/dist/web/assets/chat-tab-BG0M1D3d.js.br +0 -3
  322. package/dist/web/assets/chat-tab-BG0M1D3d.js.gz +0 -0
  323. package/dist/web/assets/chat-tab-DsW93LTL.js.br +0 -0
  324. package/dist/web/assets/chat-tab-DsW93LTL.js.gz +0 -0
  325. package/dist/web/assets/code-editor-AxLIpK-b.js.br +0 -0
  326. package/dist/web/assets/code-editor-AxLIpK-b.js.gz +0 -0
  327. package/dist/web/assets/codex-reset-credit-button-BnRmkBZu.js.br +0 -0
  328. package/dist/web/assets/codex-reset-credit-button-BnRmkBZu.js.gz +0 -0
  329. package/dist/web/assets/conflict-editor-BwuNUPAK.js.br +0 -0
  330. package/dist/web/assets/conflict-editor-BwuNUPAK.js.gz +0 -0
  331. package/dist/web/assets/connection-form-tab-CM2o80eB.js.br +0 -0
  332. package/dist/web/assets/connection-form-tab-CM2o80eB.js.gz +0 -0
  333. package/dist/web/assets/csv-preview-C7ErHm4U.js.br +0 -0
  334. package/dist/web/assets/csv-preview-C7ErHm4U.js.gz +0 -0
  335. package/dist/web/assets/db-tab-parts-C8jbkfxO.js.br +0 -0
  336. package/dist/web/assets/db-tab-parts-C8jbkfxO.js.gz +0 -0
  337. package/dist/web/assets/design-settings-section-BcGEY4Zf.js.br +0 -0
  338. package/dist/web/assets/design-settings-section-BcGEY4Zf.js.gz +0 -0
  339. package/dist/web/assets/design-tab-BGPYNFvy.js.br +0 -0
  340. package/dist/web/assets/design-tab-BGPYNFvy.js.gz +0 -0
  341. package/dist/web/assets/designs-sidebar-panel-BgMj7xRm.js.br +0 -0
  342. package/dist/web/assets/designs-sidebar-panel-BgMj7xRm.js.gz +0 -0
  343. package/dist/web/assets/diff-viewer-BKJkesHk.js.br +0 -0
  344. package/dist/web/assets/diff-viewer-BKJkesHk.js.gz +0 -0
  345. package/dist/web/assets/docx-preview-DD5oD_nL.js.br +0 -0
  346. package/dist/web/assets/docx-preview-DD5oD_nL.js.gz +0 -0
  347. package/dist/web/assets/editor-dialog--07lKJxl.js.br +0 -0
  348. package/dist/web/assets/editor-dialog--07lKJxl.js.gz +0 -0
  349. package/dist/web/assets/editor-language-service-51xjO5d3.js.br +0 -0
  350. package/dist/web/assets/editor-language-service-51xjO5d3.js.gz +0 -0
  351. package/dist/web/assets/explorer-body-BXK5SjJQ.js.br +0 -0
  352. package/dist/web/assets/explorer-body-BXK5SjJQ.js.gz +0 -0
  353. package/dist/web/assets/explorer-window-content-DKXcCmuX.js.br +0 -0
  354. package/dist/web/assets/explorer-window-content-DKXcCmuX.js.gz +0 -0
  355. package/dist/web/assets/extension-manager-section-CTpQP6te.js.br +0 -0
  356. package/dist/web/assets/extension-manager-section-CTpQP6te.js.gz +0 -0
  357. package/dist/web/assets/extension-webview-COT-yLrM.js.br +0 -0
  358. package/dist/web/assets/extension-webview-COT-yLrM.js.gz +0 -0
  359. package/dist/web/assets/files-settings-section-CgVR0fqi.js.br +0 -0
  360. package/dist/web/assets/files-settings-section-CgVR0fqi.js.gz +0 -0
  361. package/dist/web/assets/general-settings-section-BEMIbE4Y.js.br +0 -0
  362. package/dist/web/assets/general-settings-section-BEMIbE4Y.js.gz +0 -0
  363. package/dist/web/assets/git-log-panel-Cp2174iI.js.br +0 -0
  364. package/dist/web/assets/git-log-panel-Cp2174iI.js.gz +0 -0
  365. package/dist/web/assets/git-review-tab-CpMJ0qDi.js.br +0 -0
  366. package/dist/web/assets/git-review-tab-CpMJ0qDi.js.gz +0 -0
  367. package/dist/web/assets/glide-data-grid-BAhwWUhO.js.br +0 -0
  368. package/dist/web/assets/glide-data-grid-BAhwWUhO.js.gz +0 -0
  369. package/dist/web/assets/grid-save-dialog-DmJhaWOl.js.br +0 -0
  370. package/dist/web/assets/grid-save-dialog-DmJhaWOl.js.gz +0 -0
  371. package/dist/web/assets/group-chat-tab-Bq7-Yegf.js.br +0 -0
  372. package/dist/web/assets/group-chat-tab-Bq7-Yegf.js.gz +0 -0
  373. package/dist/web/assets/impexp-tab-COx3Cvs7.js.br +0 -0
  374. package/dist/web/assets/impexp-tab-COx3Cvs7.js.gz +0 -0
  375. package/dist/web/assets/index-BGk8EFQb.js.br +0 -0
  376. package/dist/web/assets/index-BGk8EFQb.js.gz +0 -0
  377. package/dist/web/assets/keyboard-shortcuts-section-Bqg7AZiZ.js.br +0 -0
  378. package/dist/web/assets/keyboard-shortcuts-section-Bqg7AZiZ.js.gz +0 -0
  379. package/dist/web/assets/language-servers-section-CJ47DSIA.js.br +0 -0
  380. package/dist/web/assets/language-servers-section-CJ47DSIA.js.gz +0 -0
  381. package/dist/web/assets/logs-app-BK-n-y_K.js.br +0 -0
  382. package/dist/web/assets/logs-app-BK-n-y_K.js.gz +0 -0
  383. package/dist/web/assets/lsp-client-DHSaAyJW.js.br +0 -0
  384. package/dist/web/assets/lsp-client-DHSaAyJW.js.gz +0 -0
  385. package/dist/web/assets/markdown-renderer-CpxdNG0f.js.br +0 -0
  386. package/dist/web/assets/markdown-renderer-CpxdNG0f.js.gz +0 -0
  387. package/dist/web/assets/mobile-explorer-sheet-BBwXGmXA.js.br +0 -0
  388. package/dist/web/assets/mobile-explorer-sheet-BBwXGmXA.js.gz +0 -0
  389. package/dist/web/assets/new-design-dialog-B_ZOvGhR.js.br +0 -0
  390. package/dist/web/assets/new-design-dialog-B_ZOvGhR.js.gz +0 -0
  391. package/dist/web/assets/notifications-settings-section-D7fIVFHe.js +0 -1
  392. package/dist/web/assets/notifications-settings-section-D7fIVFHe.js.br +0 -0
  393. package/dist/web/assets/notifications-settings-section-D7fIVFHe.js.gz +0 -0
  394. package/dist/web/assets/ppmbot-settings-section-JDvyI-gZ.js.br +0 -0
  395. package/dist/web/assets/ppmbot-settings-section-JDvyI-gZ.js.gz +0 -0
  396. package/dist/web/assets/problems-panel-Bsv_L6T2.js.br +0 -0
  397. package/dist/web/assets/problems-panel-Bsv_L6T2.js.gz +0 -0
  398. package/dist/web/assets/process-table-3kZyWOJx.js.br +0 -0
  399. package/dist/web/assets/process-table-3kZyWOJx.js.gz +0 -0
  400. package/dist/web/assets/proxy-settings-section-CYcmIp7W.js.br +0 -0
  401. package/dist/web/assets/proxy-settings-section-CYcmIp7W.js.gz +0 -0
  402. package/dist/web/assets/query-tab-CjrWnQUl.js.br +0 -0
  403. package/dist/web/assets/query-tab-CjrWnQUl.js.gz +0 -0
  404. package/dist/web/assets/remote-access-settings-section-iQkakiVx.js.br +0 -0
  405. package/dist/web/assets/remote-access-settings-section-iQkakiVx.js.gz +0 -0
  406. package/dist/web/assets/remote-desktop-clipboard-notice-CFLnb7ML.js.br +0 -0
  407. package/dist/web/assets/remote-desktop-clipboard-notice-CFLnb7ML.js.gz +0 -0
  408. package/dist/web/assets/remote-desktop-mobile-sheet-C_-_-Nkz.js.br +0 -0
  409. package/dist/web/assets/remote-desktop-mobile-sheet-C_-_-Nkz.js.gz +0 -0
  410. package/dist/web/assets/remote-desktop-mobile-view-BnHSrind.js.br +0 -0
  411. package/dist/web/assets/remote-desktop-mobile-view-BnHSrind.js.gz +0 -0
  412. package/dist/web/assets/remote-desktop-readiness-gate-BGHJ7Sv6.js.br +0 -0
  413. package/dist/web/assets/remote-desktop-readiness-gate-BGHJ7Sv6.js.gz +0 -0
  414. package/dist/web/assets/remote-desktop-window-content-BBm1YwM8.js.br +0 -0
  415. package/dist/web/assets/remote-desktop-window-content-BBm1YwM8.js.gz +0 -0
  416. package/dist/web/assets/rename-object-dialog-BLnF9xyn.js.br +0 -0
  417. package/dist/web/assets/rename-object-dialog-BLnF9xyn.js.gz +0 -0
  418. package/dist/web/assets/review-code-DG7sCIzr.js.br +0 -0
  419. package/dist/web/assets/review-code-DG7sCIzr.js.gz +0 -0
  420. package/dist/web/assets/save-as-dialog-KW7cErWh.js.br +0 -0
  421. package/dist/web/assets/save-as-dialog-KW7cErWh.js.gz +0 -0
  422. package/dist/web/assets/schedules-settings-section-B4AV4ORv.js.br +0 -0
  423. package/dist/web/assets/schedules-settings-section-B4AV4ORv.js.gz +0 -0
  424. package/dist/web/assets/session-review-tab-C5nPyBzo.js.br +0 -0
  425. package/dist/web/assets/session-review-tab-C5nPyBzo.js.gz +0 -0
  426. package/dist/web/assets/settings-body-xWSmRXUD.js.br +0 -0
  427. package/dist/web/assets/settings-body-xWSmRXUD.js.gz +0 -0
  428. package/dist/web/assets/settings-rows-ChKBHk07.js.br +0 -0
  429. package/dist/web/assets/settings-rows-ChKBHk07.js.gz +0 -0
  430. package/dist/web/assets/settings-section-content-BOh2jaWV.js +0 -1
  431. package/dist/web/assets/settings-section-content-DdVdyPVX.js.br +0 -0
  432. package/dist/web/assets/settings-section-content-DdVdyPVX.js.gz +0 -0
  433. package/dist/web/assets/slash-command-picker-Cn2Fdz2p.js.br +0 -0
  434. package/dist/web/assets/slash-command-picker-Cn2Fdz2p.js.gz +0 -0
  435. package/dist/web/assets/sql-object-tab-_mPRCvD9.js.br +0 -0
  436. package/dist/web/assets/sql-object-tab-_mPRCvD9.js.gz +0 -0
  437. package/dist/web/assets/sqlite-viewer-B8VZt-mA.js.br +0 -0
  438. package/dist/web/assets/sqlite-viewer-B8VZt-mA.js.gz +0 -0
  439. package/dist/web/assets/structure-save-dialog-Lcst7kAl.js.br +0 -0
  440. package/dist/web/assets/structure-save-dialog-Lcst7kAl.js.gz +0 -0
  441. package/dist/web/assets/structure-tab-ljJCzxUe.js.br +0 -0
  442. package/dist/web/assets/structure-tab-ljJCzxUe.js.gz +0 -0
  443. package/dist/web/assets/system-monitor-body-CTi5xUZz.js.br +0 -0
  444. package/dist/web/assets/system-monitor-body-CTi5xUZz.js.gz +0 -0
  445. package/dist/web/assets/tab-host-window-content-CG0vPTc_.js.br +0 -0
  446. package/dist/web/assets/tab-host-window-content-CG0vPTc_.js.gz +0 -0
  447. package/dist/web/assets/table-tab-CTZ3nCWV.js.br +0 -0
  448. package/dist/web/assets/table-tab-CTZ3nCWV.js.gz +0 -0
  449. package/dist/web/assets/telegram-bot-token-field-DF7zq7_h.js.br +0 -0
  450. package/dist/web/assets/telegram-bot-token-field-DF7zq7_h.js.gz +0 -0
  451. package/dist/web/assets/terminal-tab-B_jECWTH.js.br +0 -0
  452. package/dist/web/assets/terminal-tab-B_jECWTH.js.gz +0 -0
  453. package/dist/web/assets/tool-cards-CsD_VXcG.js.br +0 -0
  454. package/dist/web/assets/tool-cards-CsD_VXcG.js.gz +0 -0
  455. package/dist/web/assets/tools-settings-section-DbBr0Hnr.js.br +0 -0
  456. package/dist/web/assets/tools-settings-section-DbBr0Hnr.js.gz +0 -0
  457. package/dist/web/assets/use-inline-blame-BA2mrMxT.js.br +0 -0
  458. package/dist/web/assets/use-inline-blame-BA2mrMxT.js.gz +0 -0
  459. package/dist/web/assets/use-lsp-ChbrzwvS.js.br +0 -0
  460. package/dist/web/assets/use-lsp-ChbrzwvS.js.gz +0 -0
  461. package/dist/web/assets/use-process-kill-C-YetnGd.js.br +0 -0
  462. package/dist/web/assets/use-process-kill-C-YetnGd.js.gz +0 -0
  463. package/dist/web/assets/use-sql-schema-info-pKwLD9wk.js.br +0 -0
  464. package/dist/web/assets/use-sql-schema-info-pKwLD9wk.js.gz +0 -0
  465. package/dist/web/assets/web-preview-tab-DGLPuZYM.js.br +0 -0
  466. package/dist/web/assets/web-preview-tab-DGLPuZYM.js.gz +0 -0
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Counts HTTP requests by route for the memory monitor, so growth can be put next to traffic.
3
+ *
4
+ * The server's 26/10/2026 leak kept about a file's size of native memory per *file response*
5
+ * and per transcript read. Spawn counts could not show that: it rose with requests, not with
6
+ * child processes. A per-route count and the bytes each route sent, logged beside private
7
+ * bytes, is what lets the next reader say "it grows by N MB per `/api/files/raw`".
8
+ *
9
+ * Counters are cumulative; each reader keeps its own previous copy and diffs (`diffRequestCounts`),
10
+ * because the 30-second monitor and the 30-minute summary read the same counters at different rates.
11
+ *
12
+ * A route key is the method plus the path with anything that is data — a project name, an id,
13
+ * a file path, a token — replaced by a placeholder, so the log never carries one.
14
+ */
15
+
16
+ export interface RouteCount {
17
+ count: number;
18
+ /** Bytes declared by `Content-Length`; a streamed body has none and is counted in `streamed`. */
19
+ bytes: number;
20
+ streamed: number;
21
+ }
22
+
23
+ /** Segments after which the next one is a name or a token, never a route word. */
24
+ const NAME_BEFORE = new Set(["project", "projects", "session", "sessions", "content", "grid-export", "whep", "designs", "accounts", "connections", "extensions"]);
25
+ /** A segment that reads as a route word rather than data. */
26
+ const ROUTE_WORD = /^[a-z][a-z0-9-]{0,30}$/;
27
+ const MAX_SEGMENTS = 4;
28
+ /** Distinct keys kept; a path pattern we failed to normalise must not grow the map forever. */
29
+ const MAX_KEYS = 500;
30
+
31
+ /** `GET /api/project/ppm/files/raw?path=…` → `GET /api/project/:name/files`. Exported for tests. */
32
+ export function requestRouteKey(method: string, pathname: string): string {
33
+ const segs = pathname.split("/").filter(Boolean);
34
+ if (segs[0] !== "api" && segs[0] !== "ws") {
35
+ const ext = pathname.match(/\.([a-z0-9]{1,8})$/i)?.[1]?.toLowerCase();
36
+ return `${method} static${ext ? ` .${ext}` : ""}`;
37
+ }
38
+ const out: string[] = [];
39
+ for (let i = 0; i < segs.length && out.length < MAX_SEGMENTS; i++) {
40
+ const seg = segs[i]!;
41
+ const prev = segs[i - 1];
42
+ if (prev && NAME_BEFORE.has(prev)) out.push(":name");
43
+ else out.push(ROUTE_WORD.test(seg) ? seg : ":x");
44
+ }
45
+ return `${method} /${out.join("/")}`;
46
+ }
47
+
48
+ export class RequestTracker {
49
+ private readonly counts = new Map<string, RouteCount>();
50
+ private total = 0;
51
+
52
+ record(method: string, pathname: string, contentLength: string | null): void {
53
+ this.total++;
54
+ const key = requestRouteKey(method, pathname);
55
+ let entry = this.counts.get(key);
56
+ if (!entry) {
57
+ if (this.counts.size >= MAX_KEYS) return;
58
+ entry = { count: 0, bytes: 0, streamed: 0 };
59
+ this.counts.set(key, entry);
60
+ }
61
+ entry.count++;
62
+ const n = contentLength === null ? NaN : Number(contentLength);
63
+ if (Number.isFinite(n)) entry.bytes += n; else entry.streamed++;
64
+ }
65
+
66
+ /** A copy of the cumulative counters. */
67
+ read(): { total: number; routes: Map<string, RouteCount> } {
68
+ const routes = new Map<string, RouteCount>();
69
+ for (const [k, v] of this.counts) routes.set(k, { ...v });
70
+ return { total: this.total, routes };
71
+ }
72
+ }
73
+
74
+ /** What happened between two readings: the busiest routes, by count, with MB sent. */
75
+ export function diffRequestCounts(
76
+ prev: ReturnType<RequestTracker["read"]> | undefined,
77
+ now: ReturnType<RequestTracker["read"]>,
78
+ limit: number,
79
+ ): { total: number; top: Record<string, { n: number; mb: number; streamed: number }> } {
80
+ const rows: Array<[string, number, number, number]> = [];
81
+ for (const [key, v] of now.routes) {
82
+ const p = prev?.routes.get(key);
83
+ const n = v.count - (p?.count ?? 0);
84
+ if (n <= 0) continue;
85
+ rows.push([key, n, v.bytes - (p?.bytes ?? 0), v.streamed - (p?.streamed ?? 0)]);
86
+ }
87
+ rows.sort((a, b) => b[1] - a[1]);
88
+ const top: Record<string, { n: number; mb: number; streamed: number }> = {};
89
+ for (const [key, n, bytes, streamed] of rows.slice(0, limit)) {
90
+ top[key] = { n, mb: Math.round((bytes / (1024 * 1024)) * 10) / 10, streamed };
91
+ }
92
+ return { total: now.total - (prev?.total ?? 0), top };
93
+ }
94
+
95
+ export const requestTracker = new RequestTracker();
@@ -0,0 +1,242 @@
1
+ /**
2
+ * Counts every child process the server starts, for the memory monitor.
3
+ *
4
+ * A child process is the prime suspect for native growth on Windows: each
5
+ * `powershell.exe` spawn was measured to cost Bun a 32 MiB allocator segment it never
6
+ * returns (see `system-metrics/powershell-session.ts`). A per-interval count by command,
7
+ * logged beside private bytes, is what lets a later reader say "the commit charge climbs
8
+ * by N MB per `git` spawn" instead of guessing.
9
+ *
10
+ * It wraps `Bun.spawn` and `Bun.spawnSync`, which is enough: in Bun, `node:child_process`
11
+ * (`spawn`, `exec`, `execFile`, and their sync forms — what the Claude SDK, simple-git and
12
+ * codex go through) is built on those two globals, so the wrapper sees them too. Only a
13
+ * module that saved its own reference to `Bun.spawn` before the wrapper went in is missed.
14
+ *
15
+ * A label is the program's basename plus at most one more word that is known not to be data:
16
+ * a git subcommand, a script file's name, a runner's own subcommand, or the first program a
17
+ * shell is told to run. Anything else — an inline script, an argument, a word carrying `=`,
18
+ * `$` or quotes — is left out, because an argument can be a path, a prompt or a token.
19
+ */
20
+
21
+ interface LiveChild {
22
+ pid: number;
23
+ label: string;
24
+ startedAt: number;
25
+ /** The chat session a Claude/Codex subprocess serves, from its own `--resume`/`--session-id`. */
26
+ session?: string;
27
+ /** Private bytes the last time the tree was read, so an exit can say what the child held. */
28
+ lastPrivateMB?: number;
29
+ }
30
+
31
+ /** What a long-lived child's start and exit look like to whoever logs them. */
32
+ export interface ChildLifecycleEvent {
33
+ event: "start" | "exit";
34
+ pid: number;
35
+ label: string;
36
+ session?: string;
37
+ ageMin?: number;
38
+ lastPrivateMB?: number;
39
+ }
40
+
41
+ /** Labels whose start and exit are worth a log line: few, long-lived, and each one is a chat. */
42
+ const LIFECYCLE_LABEL = /^(claude|codex)\b/;
43
+ /** Session ids are UUIDs; anything else after the flag is not taken. */
44
+ const SESSION_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
45
+ const SESSION_FLAGS = ["--resume", "--session-id"];
46
+
47
+ /** The session a command line resumes or names, when it carries one. Exported for tests. */
48
+ export function sessionOf(argv: readonly unknown[]): string | undefined {
49
+ for (let i = 0; i < argv.length; i++) {
50
+ const a = argv[i];
51
+ if (typeof a !== "string") continue;
52
+ for (const flag of SESSION_FLAGS) {
53
+ const value = a === flag ? argv[i + 1] : a.startsWith(`${flag}=`) ? a.slice(flag.length + 1) : undefined;
54
+ if (typeof value === "string" && SESSION_ID.test(value)) return value;
55
+ }
56
+ }
57
+ return undefined;
58
+ }
59
+
60
+ export interface SpawnSnapshot {
61
+ /** Spawns since the previous snapshot, by label (sync ones included). */
62
+ spawned: Record<string, number>;
63
+ /** Children still running now, by label. */
64
+ live: Record<string, number>;
65
+ liveTotal: number;
66
+ /** Every spawn since the tracker started. */
67
+ total: number;
68
+ }
69
+
70
+ /** Programs whose own name says nothing: the script or subcommand after them is the label. */
71
+ const RUNNERS = new Set(["node", "bun", "npx", "bunx", "python", "python3", "py", "deno"]);
72
+ /** Programs handed a whole command line as one argument. */
73
+ const SHELLS = new Set(["cmd", "powershell", "pwsh", "sh", "bash", "zsh"]);
74
+ /** A runner flag whose value is code to run: the label stops at the flag. */
75
+ const EVAL_FLAGS = new Set(["-e", "--eval", "-p", "--print", "-c"]);
76
+ /** A runner's own subcommands, which are words and never data. */
77
+ const RUNNER_SUBCOMMANDS = new Set(["run", "x", "exec", "install", "add", "remove", "test", "build", "upgrade"]);
78
+ /** Runner flags that take a separate value, which is not the script. */
79
+ const RUNNER_VALUE_FLAGS = new Set(["-r", "--require", "--import", "--loader", "--cwd", "--env-file", "--config", "-m"]);
80
+ /** The shell flag after which the command line comes. */
81
+ const SHELL_COMMAND_FLAGS = new Set(["-c", "/c", "/k", "-command", "-file"]);
82
+ const SCRIPT_FILE = /\.(c|m)?[jt]sx?$|\.py$|\.ps1$/i;
83
+ /** A word that is plausibly a program or subcommand name and cannot carry an assignment or a quote. */
84
+ const PLAIN_WORD = /^[A-Za-z0-9._\\/:@+-]+$/;
85
+ const GIT_VALUE_FLAGS = new Set(["-c", "-C", "--git-dir", "--work-tree", "--namespace", "--exec-path"]);
86
+
87
+ /** Lowercased basename with the executable extension dropped, cut to 40 characters. */
88
+ function baseName(path: string): string {
89
+ const name = path.split(/[\\/]/).pop() ?? "";
90
+ return name.toLowerCase().replace(/\.(exe|cmd|bat|com)$/, "").slice(0, 40);
91
+ }
92
+
93
+ function gitLabel(rest: string[]): string {
94
+ for (let i = 0; i < rest.length; i++) {
95
+ const a = rest[i]!;
96
+ if (GIT_VALUE_FLAGS.has(a)) { i++; continue; }
97
+ if (a.startsWith("-")) continue;
98
+ return /^[a-z][a-z0-9-]*$/.test(a) ? `git ${a}` : "git";
99
+ }
100
+ return "git";
101
+ }
102
+
103
+ function runnerLabel(program: string, rest: string[]): string {
104
+ for (let i = 0; i < rest.length; i++) {
105
+ const a = rest[i]!;
106
+ if (EVAL_FLAGS.has(a)) return `${program} ${a}`;
107
+ if (RUNNER_VALUE_FLAGS.has(a)) { i++; continue; }
108
+ if (a.startsWith("-")) continue;
109
+ if (!PLAIN_WORD.test(a)) return program;
110
+ if (SCRIPT_FILE.test(a)) return `${program} ${baseName(a)}`;
111
+ if (RUNNER_SUBCOMMANDS.has(a)) return `${program} ${a}`;
112
+ // `npx <package>`: the package's own name, its scope and version dropped.
113
+ const pkg = a.match(/^(?:@[\w.-]+\/)?([\w.-]+)(?:@[\w.^~-]+)?$/);
114
+ if ((program === "npx" || program === "bunx") && pkg) return `${program} ${pkg[1]!.toLowerCase().slice(0, 40)}`;
115
+ return program;
116
+ }
117
+ return program;
118
+ }
119
+
120
+ function shellLabel(program: string, rest: string[]): string {
121
+ const at = rest.findIndex((a) => SHELL_COMMAND_FLAGS.has(a.toLowerCase()));
122
+ const line = at === -1 ? undefined : rest[at + 1];
123
+ if (line === undefined) return program;
124
+ const first = line.trim().split(/\s+/)[0] ?? "";
125
+ if (!first || !PLAIN_WORD.test(first)) return program;
126
+ return `${program} ${baseName(first)}`;
127
+ }
128
+
129
+ /** A short, argument-free name for a command line. Exported for tests. */
130
+ export function spawnLabel(argv: readonly unknown[]): string {
131
+ const parts = argv.filter((a): a is string => typeof a === "string");
132
+ const [head, ...rest] = parts;
133
+ if (head === undefined) return "unknown";
134
+ const program = baseName(head.replace(/^["']|["']$/g, "")).replace(/[^a-z0-9._+-]/g, "");
135
+ if (!program) return "unknown";
136
+ if (program === "git") return gitLabel(rest);
137
+ if (RUNNERS.has(program)) return runnerLabel(program, rest);
138
+ if (SHELLS.has(program)) return shellLabel(program, rest);
139
+ return program;
140
+ }
141
+
142
+ /** `Bun.spawn(cmd[], opts)` or `Bun.spawn({ cmd, ... })`. */
143
+ function argvOf(args: unknown[]): readonly unknown[] {
144
+ const first = args[0];
145
+ if (Array.isArray(first)) return first;
146
+ if (first && typeof first === "object" && Array.isArray((first as { cmd?: unknown }).cmd)) {
147
+ return (first as { cmd: unknown[] }).cmd;
148
+ }
149
+ return [];
150
+ }
151
+
152
+ export class SpawnTracker {
153
+ /** Keyed by the child object, not its pid: a pid can be reused before the old child's `exited` settles. */
154
+ private readonly live = new Map<object, LiveChild>();
155
+ private interval = new Map<string, number>();
156
+ private readonly totals = new Map<string, number>();
157
+ private total = 0;
158
+ private installed = false;
159
+ /** Set by the memory watchdog to log long-lived children coming and going. */
160
+ onLifecycle?: (e: ChildLifecycleEvent) => void;
161
+
162
+ constructor(private readonly now: () => number = Date.now) {}
163
+
164
+ /** Record one spawn. Exported for tests; `install()` calls it for real spawns. */
165
+ record(label: string, child?: { pid?: number; exited?: Promise<unknown> }, session?: string): void {
166
+ this.total++;
167
+ this.interval.set(label, (this.interval.get(label) ?? 0) + 1);
168
+ this.totals.set(label, (this.totals.get(label) ?? 0) + 1);
169
+ const pid = child?.pid;
170
+ if (!child || typeof pid !== "number" || !child.exited) return;
171
+ const entry: LiveChild = { pid, label, startedAt: this.now(), session };
172
+ this.live.set(child, entry);
173
+ const logged = LIFECYCLE_LABEL.test(label);
174
+ if (logged) this.emit({ event: "start", pid, label, session });
175
+ const done = () => {
176
+ this.live.delete(child);
177
+ if (!logged) return;
178
+ this.emit({
179
+ event: "exit", pid, label, session,
180
+ ageMin: Math.round((this.now() - entry.startedAt) / 60_000),
181
+ lastPrivateMB: entry.lastPrivateMB,
182
+ });
183
+ };
184
+ child.exited.then(done, done);
185
+ }
186
+
187
+ private emit(e: ChildLifecycleEvent): void {
188
+ try { this.onLifecycle?.(e); } catch { /* a log line must never break a spawn */ }
189
+ }
190
+
191
+ /** What the tracker knows about a live direct child, by pid. */
192
+ describe(pid: number): LiveChild | undefined {
193
+ for (const c of this.live.values()) if (c.pid === pid) return c;
194
+ return undefined;
195
+ }
196
+
197
+ /** Wrap the two globals. Safe to call twice. */
198
+ install(): void {
199
+ if (this.installed) return;
200
+ this.installed = true;
201
+ const bun = Bun as unknown as Record<"spawn" | "spawnSync", (...a: unknown[]) => unknown>;
202
+ const spawn = bun.spawn;
203
+ const spawnSync = bun.spawnSync;
204
+ bun.spawn = (...args: unknown[]) => {
205
+ const child = spawn.apply(Bun, args) as { pid?: number; exited?: Promise<unknown> };
206
+ try {
207
+ const argv = argvOf(args);
208
+ this.record(spawnLabel(argv), child, sessionOf(argv));
209
+ } catch { /* counting must never break a spawn */ }
210
+ return child;
211
+ };
212
+ bun.spawnSync = (...args: unknown[]) => {
213
+ try { this.record(spawnLabel(argvOf(args))); } catch { /* as above */ }
214
+ return spawnSync.apply(Bun, args);
215
+ };
216
+ }
217
+
218
+ /** Counts since the last call, plus what is alive now. Resets the interval counts. */
219
+ snapshot(): SpawnSnapshot {
220
+ const live: Record<string, number> = {};
221
+ for (const c of this.live.values()) live[c.label] = (live[c.label] ?? 0) + 1;
222
+ const spawned = Object.fromEntries(this.interval);
223
+ this.interval = new Map();
224
+ return { spawned, live, liveTotal: this.live.size, total: this.total };
225
+ }
226
+
227
+ /** The longest-running children, for a threshold alert. */
228
+ oldestLive(limit: number): Array<{ pid: number; label: string; ageMin: number }> {
229
+ const t = this.now();
230
+ return [...this.live.values()]
231
+ .sort((a, b) => a.startedAt - b.startedAt)
232
+ .slice(0, limit)
233
+ .map((c) => ({ pid: c.pid, label: c.label, ageMin: Math.round((t - c.startedAt) / 60_000) }));
234
+ }
235
+
236
+ /** Spawns by label since the tracker started, most first. */
237
+ topTotals(limit: number): Record<string, number> {
238
+ return Object.fromEntries([...this.totals.entries()].sort((a, b) => b[1] - a[1]).slice(0, limit));
239
+ }
240
+ }
241
+
242
+ export const spawnTracker = new SpawnTracker();
@@ -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
  }