@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
@@ -15,9 +15,20 @@ import {
15
15
  } from "../services/tab-tools-mcp/tab-tools-mcp-tool.ts";
16
16
  import { TAB_TOOLS } from "../shared/ppm-tools.ts";
17
17
  import { CLAUDE_DB_TOOLS_MCP_SERVER, DB_TOOLS, DB_TOOLS_TIMEOUT_MS, type DbToolsMcpAccess } from "../services/db-ai-tools/db-ai-tools-tool.ts";
18
+ import { EFFORT_LEVELS, type EffortLevel } from "../shared/effort-status.ts";
18
19
 
19
- export const VALID_EFFORT_VALUES = ["low", "medium", "high", "xhigh", "max"] as const;
20
- export type EffortValue = (typeof VALID_EFFORT_VALUES)[number];
20
+ export const VALID_EFFORT_VALUES = EFFORT_LEVELS;
21
+ export type EffortValue = EffortLevel;
22
+
23
+ export type ClaudeSettingSource = "user" | "project" | "local";
24
+
25
+ /**
26
+ * The settings files a chat's CLI loads. `local` stays out: a project's
27
+ * `.claude/settings.local.json` can deny tools and set `env` (an `ANTHROPIC_API_KEY` there moves
28
+ * the chat onto another account), and the empty `settings` PPM passes cancels neither. The
29
+ * project's own `.claude/settings.json` is loaded and can do the same; only the local file is out.
30
+ */
31
+ export const CLAUDE_SETTING_SOURCES: readonly ClaudeSettingSource[] = ["user", "project"];
21
32
 
22
33
  /**
23
34
  * Thinking is a tri-state carried as a nullable integer through config, the DB and
@@ -7,7 +7,7 @@ import {
7
7
  getSessionInfo as sdkGetSessionInfo,
8
8
  getSessionMessages,
9
9
  } from "@anthropic-ai/claude-agent-sdk";
10
- import { allowedToolsFor, buildModelQueryOptions, buildSystemPromptOption, buildToolHooks, CLAUDE_DB_TOOLS, CLAUDE_TAB_TOOLS, dbToolsMcpServers, designMcpServers, fileWriteTarget, preToolUseDecision, READ_ONLY_TOOLS, shellHookCall, tabToolsMcpServers, withSessionTokenMasked } from "./claude-agent-sdk-query-options.ts";
10
+ import { allowedToolsFor, buildModelQueryOptions, CLAUDE_SETTING_SOURCES, buildSystemPromptOption, buildToolHooks, CLAUDE_DB_TOOLS, CLAUDE_TAB_TOOLS, dbToolsMcpServers, designMcpServers, fileWriteTarget, preToolUseDecision, READ_ONLY_TOOLS, shellHookCall, tabToolsMcpServers, withSessionTokenMasked } from "./claude-agent-sdk-query-options.ts";
11
11
  import { localServerBaseUrl } from "../services/server-listen-address.ts";
12
12
  import { TAB_TOOLS_MCP_PATH, tabToolsMcpAccessFor } from "../services/tab-tools-mcp/tab-tools-mcp-tokens.ts";
13
13
  import { DB_TOOLS_MCP_PATH, dbToolsMcpAccessFor } from "../services/db-ai-tools/db-ai-tools-tokens.ts";
@@ -19,6 +19,10 @@ import { captureBaseline } from "../services/session-file-baselines/session-file
19
19
  import { observeFile } from "../services/session-file-baselines/session-file-history.ts";
20
20
  import { beginShellCommand, endShellCommand, noteFileToolWrite } from "../services/session-file-baselines/shell-change-tracker.ts";
21
21
  import { WarmSpares, spawnFingerprint } from "./claude-warm-spare.ts";
22
+ import { APPROVAL_WAKE, ApprovalQueue } from "./claude-approval-queue.ts";
23
+ import { EFFORT_ENV_VAR, effortStatusFromCli, estimateEffortStatus, userSettingsFile, type EffortSettingsQuery } from "./claude-effort-status.ts";
24
+ import type { EffortStatus } from "../shared/effort-status.ts";
25
+ import { withTimeout } from "../services/mcp-oauth/mcp-control-query.ts";
22
26
  import { CLAUDE_DESIGN_CHECK_TOOL } from "../services/design/mcp/design-mcp-tool.ts";
23
27
  import { designToolDecision } from "../services/design/design-tool-policy.ts";
24
28
  import { CLAUDE_MODELS } from "../types/claude-models.ts";
@@ -62,6 +66,8 @@ const usageLog = createLogger("usage");
62
66
  const mcpControlLog = createLogger("mcp-control");
63
67
 
64
68
  const CLAUDE_PROJECTS_DIR = resolve(homedir(), ".claude/projects");
69
+ /** A running CLI answers `get_settings` in a few ms; this only bounds one that never does. */
70
+ const EFFORT_READ_TIMEOUT_MS = 5_000;
65
71
 
66
72
  /**
67
73
  * Resolve the Claude CLI the SDK should spawn.
@@ -283,6 +289,8 @@ export class ClaudeAgentSdkProvider implements AIProvider {
283
289
  * and this is the only place that knows what made it do so.
284
290
  */
285
291
  private teardownReasons = new Map<string, string>();
292
+ /** The `--effort` each session's CLI was started with, to set beside what it reports sending. */
293
+ private startedEffort = new Map<string, string | null>();
286
294
  /** The API's last rate-limit status per account and limit, so only a change is logged. */
287
295
  private rateLimitStatus = new Map<string, string>();
288
296
  /** CLIs started for chats that do not exist yet — see `prewarm`. */
@@ -738,6 +746,7 @@ export class ClaudeAgentSdkProvider implements AIProvider {
738
746
  }
739
747
  }
740
748
  this.forkSources.delete(sessionId);
749
+ this.startedEffort.delete(sessionId);
741
750
 
742
751
  // Best-effort: delete JSONL from ~/.claude/projects/
743
752
  try {
@@ -933,7 +942,7 @@ export class ClaudeAgentSdkProvider implements AIProvider {
933
942
  ...(p.forkSession && { forkSession: true }),
934
943
  cwd: p.cwd,
935
944
  systemPrompt: p.systemPrompt,
936
- settingSources: ["user", "project"],
945
+ settingSources: [...CLAUDE_SETTING_SOURCES],
937
946
  env: p.env,
938
947
  settings: { permissions: { allow: [], deny: [] } },
939
948
  allowedTools: p.allowedTools,
@@ -1177,12 +1186,8 @@ export class ClaudeAgentSdkProvider implements AIProvider {
1177
1186
  const dbToolsMcp = opts?.designSession ? undefined : opts?.dbToolsMcp;
1178
1187
  const allowedTools = allowedToolsFor({ isBypass, agentTeams: providerConfig.agent_teams, designPolicy, designCheckTool });
1179
1188
 
1180
- /**
1181
- * Approval events to yield from the generator.
1182
- * PreToolUse hook pushes events here; the main loop yields them.
1183
- */
1184
- const approvalEvents: ChatEvent[] = [];
1185
- let approvalNotify: (() => void) | undefined;
1189
+ /** Approval requests from canUseTool and the PreToolUse hook, handed out by the main loop. */
1190
+ const approvals = new ApprovalQueue<ChatEvent>();
1186
1191
 
1187
1192
  /**
1188
1193
  * Helper: send approval request to FE and wait for response.
@@ -1193,8 +1198,7 @@ export class ClaudeAgentSdkProvider implements AIProvider {
1193
1198
  const promise = new Promise<{ approved: boolean; data?: unknown }>((resolve) => {
1194
1199
  this.pendingApprovals.set(requestId, { resolve, sessionId });
1195
1200
  });
1196
- approvalEvents.push({ type: "approval_request", requestId, tool: toolName, input });
1197
- approvalNotify?.();
1201
+ approvals.push({ type: "approval_request", requestId, tool: toolName, input });
1198
1202
  return promise;
1199
1203
  };
1200
1204
 
@@ -1460,6 +1464,8 @@ export class ClaudeAgentSdkProvider implements AIProvider {
1460
1464
  providerConfig,
1461
1465
  stderr: stderrCallback,
1462
1466
  });
1467
+ // Every retry below restarts the CLI with these same options.
1468
+ this.startedEffort.set(sessionId, (queryOptions.effort as string | undefined) ?? null);
1463
1469
 
1464
1470
  // Crash retry: if subprocess exits with non-zero code before producing events,
1465
1471
  // clean up and retry once with a fresh query before surfacing the error.
@@ -1610,7 +1616,12 @@ export class ClaudeAgentSdkProvider implements AIProvider {
1610
1616
  assistantContent = "";
1611
1617
  pendingToolCount = 0;
1612
1618
  let sdkEventCount = 0;
1613
- for await (const msg of eventSource) {
1619
+ for await (const msg of approvals.alongside(eventSource)) {
1620
+ // An approval goes out now: the CLI sends nothing more until it is answered.
1621
+ if (msg === APPROVAL_WAKE) {
1622
+ yield* approvals.drain();
1623
+ continue;
1624
+ }
1614
1625
  sdkEventCount++;
1615
1626
  hadAnyEvents = true;
1616
1627
  if (sdkEventCount === 1) {
@@ -1640,9 +1651,7 @@ export class ClaudeAgentSdkProvider implements AIProvider {
1640
1651
  const parentId = (msg as any).parent_tool_use_id as string | undefined;
1641
1652
 
1642
1653
  // Yield any queued approval events
1643
- while (approvalEvents.length > 0) {
1644
- yield approvalEvents.shift()!;
1645
- }
1654
+ yield* approvals.drain();
1646
1655
 
1647
1656
  // Log system events for debugging SDK lifecycle: the subtype only, never the message.
1648
1657
  // thinking_tokens and status=requesting arrive at token rate, so not even at DEBUG.
@@ -2430,9 +2439,7 @@ export class ClaudeAgentSdkProvider implements AIProvider {
2430
2439
  }
2431
2440
 
2432
2441
  // Yield remaining approval events
2433
- while (approvalEvents.length > 0) {
2434
- yield approvalEvents.shift()!;
2435
- }
2442
+ yield* approvals.drain();
2436
2443
 
2437
2444
  if (!hadAnyEvents) {
2438
2445
  yield { type: "error", message: "Claude did not respond. Check that 'claude' CLI works in your terminal." };
@@ -2533,7 +2540,7 @@ export class ClaudeAgentSdkProvider implements AIProvider {
2533
2540
  ...(needsNodeInterpreter(process.platform, cliExecutablePath) && { executable: "node" }),
2534
2541
  ...(cliExecutablePath && { pathToClaudeCodeExecutable: cliExecutablePath }),
2535
2542
  cwd,
2536
- settingSources: ["user", "project"],
2543
+ settingSources: [...CLAUDE_SETTING_SOURCES],
2537
2544
  // No account: MCP OAuth tokens belong to the CLI's own credential store, the same
2538
2545
  // one an interactive `claude` writes, and no model call is ever made here.
2539
2546
  env: this.buildQueryEnv(cwd, null),
@@ -2578,6 +2585,39 @@ export class ClaudeAgentSdkProvider implements AIProvider {
2578
2585
  }
2579
2586
  }
2580
2587
 
2588
+ /**
2589
+ * The effort this session's CLI sends and what overrides the pick, asked of the CLI itself:
2590
+ * `CLAUDE_CODE_EFFORT_LEVEL`, a settings cap, an organization limit and the model's own levels
2591
+ * all count there, while the `--effort` PPM passed is only one input to it. Null when the
2592
+ * session has no live subprocess or the CLI does not answer.
2593
+ */
2594
+ async readEffortStatus(sessionId: string): Promise<EffortStatus | null> {
2595
+ const ss = this.streamingSessions.get(sessionId);
2596
+ if (!ss) return null;
2597
+ const cli = ss.query as unknown as EffortSettingsQuery;
2598
+ try {
2599
+ const [answer, init] = await Promise.all([
2600
+ withTimeout(cli.getSettings(), EFFORT_READ_TIMEOUT_MS, "Claude did not report its settings"),
2601
+ withTimeout(cli.initializationResult(), EFFORT_READ_TIMEOUT_MS, "Claude did not report its models").catch(() => undefined),
2602
+ ]);
2603
+ return effortStatusFromCli({
2604
+ answer,
2605
+ models: init?.models,
2606
+ requested: this.startedEffort.get(sessionId),
2607
+ processEnv: process.env[EFFORT_ENV_VAR],
2608
+ userFile: userSettingsFile(process.env).shown,
2609
+ });
2610
+ } catch (e) {
2611
+ log.debug(`session=${sessionId} effort status unavailable: ${(e as Error).message}`);
2612
+ return null;
2613
+ }
2614
+ }
2615
+
2616
+ /** What the settings files say about effort for a chat in `projectPath` no CLI has answered for yet. */
2617
+ estimateEffortStatus(projectPath: string | undefined, model?: string): EffortStatus {
2618
+ return estimateEffortStatus({ projectPath, model, env: process.env });
2619
+ }
2620
+
2581
2621
  /** Abort and fully teardown the streaming session — user must resume to continue */
2582
2622
  abortQuery(sessionId: string, source = "unknown"): void {
2583
2623
  const ss = this.streamingSessions.get(sessionId);
@@ -0,0 +1,75 @@
1
+ /** What `alongside` yields instead of a message when only the queue has something to hand out. */
2
+ export const APPROVAL_WAKE = Symbol("approval-wake");
3
+
4
+ /**
5
+ * Approval requests raised from the CLI's callbacks (`canUseTool`, the PreToolUse hook), for the
6
+ * turn's loop over the CLI's messages to hand out.
7
+ *
8
+ * The CLI asks through a control request and then says nothing until it is answered, so a queue
9
+ * looked at only when the next message arrives can wait forever: an AskUserQuestion stayed behind
10
+ * a thinking spinner for 17 minutes, until the user cancelled. Whether the turn's last message
11
+ * landed before or after the callback ran decided it, which is why it looked intermittent.
12
+ */
13
+ export class ApprovalQueue<E> {
14
+ private readonly queued: E[] = [];
15
+ private wake: (() => void) | undefined;
16
+
17
+ push(event: E): void {
18
+ this.queued.push(event);
19
+ this.wake?.();
20
+ }
21
+
22
+ *drain(): Generator<E> {
23
+ while (this.queued.length > 0) yield this.queued.shift()!;
24
+ }
25
+
26
+ /**
27
+ * `source`'s items, plus `APPROVAL_WAKE` whenever something is queued and `source` has nothing
28
+ * to give. The caller must drain the queue on a wake, or it is woken again at once.
29
+ */
30
+ async *alongside<T>(source: AsyncIterable<T>): AsyncGenerator<T | typeof APPROVAL_WAKE> {
31
+ const it = source[Symbol.asyncIterator]();
32
+ // Kept across wakes: a second next() would hand this item to a promise nobody reads.
33
+ let next: Promise<IteratorResult<T>> | undefined;
34
+ // Closed on an early exit, as `for await` closes it: not once it has ended or thrown.
35
+ let open = true;
36
+ try {
37
+ for (;;) {
38
+ if (this.queued.length > 0) {
39
+ yield APPROVAL_WAKE;
40
+ continue;
41
+ }
42
+ const pending = (next ??= it.next());
43
+ let got: IteratorResult<T> | typeof APPROVAL_WAKE;
44
+ try {
45
+ got = await new Promise<IteratorResult<T> | typeof APPROVAL_WAKE>((resolve, reject) => {
46
+ this.wake = () => resolve(APPROVAL_WAKE);
47
+ // next() may have run a callback that queued something before there was a wake to call.
48
+ if (this.queued.length > 0) this.wake();
49
+ pending.then(resolve, reject);
50
+ });
51
+ } catch (e) {
52
+ open = false;
53
+ throw e;
54
+ } finally {
55
+ this.wake = undefined;
56
+ }
57
+ if (got === APPROVAL_WAKE) continue;
58
+ next = undefined;
59
+ if (got.done) {
60
+ open = false;
61
+ return;
62
+ }
63
+ yield got.value;
64
+ }
65
+ } finally {
66
+ if (open && next) {
67
+ // Not awaited: an async generator's return() waits behind the next() still out, which
68
+ // the CLI answers only after the approval it is waiting on.
69
+ Promise.resolve().then(() => it.return?.()).catch(() => {});
70
+ } else if (open) {
71
+ await it.return?.();
72
+ }
73
+ }
74
+ }
75
+ }
@@ -0,0 +1,211 @@
1
+ /**
2
+ * What decides a Claude session's effort besides the pick, worked out from the same settings
3
+ * the CLI reads. The live answer comes from the running CLI (`readEffortStatus` in the
4
+ * provider); until a turn has started one, `estimateEffortStatus` reads the settings files
5
+ * the CLI would, which is enough for the common overrides and is replaced by the CLI's own
6
+ * answer as soon as there is one.
7
+ */
8
+ import { readFileSync } from "node:fs";
9
+ import { homedir } from "node:os";
10
+ import * as nodePath from "node:path";
11
+ import { join } from "node:path";
12
+ import {
13
+ effortEnvMeaning, effortRank, isEffortLevel,
14
+ type EffortLevel, type EffortSetting, type EffortSettingSource, type EffortStatus,
15
+ } from "../shared/effort-status.ts";
16
+ import { CLAUDE_SETTING_SOURCES, type ClaudeSettingSource } from "./claude-agent-sdk-query-options.ts";
17
+
18
+ export const EFFORT_ENV_VAR = "CLAUDE_CODE_EFFORT_LEVEL";
19
+
20
+ /** One entry of `get_settings`'s `sources`, which the CLI orders from lowest priority to highest. */
21
+ export interface CliSettingsSource {
22
+ source: string;
23
+ settings: Record<string, unknown>;
24
+ }
25
+
26
+ /**
27
+ * The part of the `get_settings` answer read here. `getSettings()` is on the SDK's `Query`
28
+ * object but left out of `sdk.d.ts`, which is why this is typed here; a test asserts the SDK
29
+ * still has it, so an upgrade that renames it fails loudly instead of the status going quiet.
30
+ */
31
+ export interface CliSettingsAnswer {
32
+ sources?: Array<CliSettingsSource | null>;
33
+ /** What the session sends after env overrides, caps and model downgrades. */
34
+ applied?: { model?: string; effort?: string | null };
35
+ }
36
+
37
+ /** A model as the CLI's `initialize` answer lists it. */
38
+ export interface CliModelInfo {
39
+ value: string;
40
+ resolvedModel?: string;
41
+ supportedEffortLevels?: string[];
42
+ }
43
+
44
+ export interface EffortSettingsQuery {
45
+ getSettings(): Promise<CliSettingsAnswer>;
46
+ initializationResult(): Promise<{ models?: CliModelInfo[] }>;
47
+ }
48
+
49
+ type PathApi = Pick<typeof nodePath, "join" | "relative" | "isAbsolute" | "sep">;
50
+
51
+ /**
52
+ * Where the CLI keeps its user settings; `~/` stands in for the home folder in what is shown.
53
+ * Decided with `relative` rather than a `${home}/` prefix, which never matches a Windows path.
54
+ */
55
+ export function userSettingsFile(
56
+ env: Record<string, string | undefined>,
57
+ home = homedir(),
58
+ path: PathApi = nodePath,
59
+ ): { path: string; shown: string } {
60
+ const file = path.join(env.CLAUDE_CONFIG_DIR || path.join(home, ".claude"), "settings.json");
61
+ const rel = path.relative(home, file);
62
+ const inHome = rel !== "" && rel !== ".." && !rel.startsWith(`..${path.sep}`) && !path.isAbsolute(rel);
63
+ return { path: file, shown: inHome ? `~/${rel.split(path.sep).join("/")}` : file };
64
+ }
65
+
66
+ const FILE_OF: Partial<Record<EffortSettingSource, string>> = {
67
+ projectSettings: ".claude/settings.json",
68
+ localSettings: ".claude/settings.local.json",
69
+ };
70
+
71
+ function place(source: string, userFile: string): Pick<EffortSetting<unknown>, "source" | "file"> {
72
+ const known = source as EffortSettingSource;
73
+ const file = source === "userSettings" ? userFile : FILE_OF[known];
74
+ return { source: known, ...(file && { file }) };
75
+ }
76
+
77
+ /** `modelSettings` keys name a model the way its dated, `[1m]`, Bedrock and Vertex spellings all match. */
78
+ function modelKey(model: string): string {
79
+ return model.toLowerCase()
80
+ .replace(/\[[^\]]*\]$/, "")
81
+ .replace(/@.*$/, "")
82
+ .replace(/-v\d+(?::\d+)?$/, "")
83
+ .replace(/^(?:[a-z-]+\.)?anthropic\./, "")
84
+ .replace(/-\d{8}$/, "");
85
+ }
86
+
87
+ /** A source's own cap for the model: a matching `modelSettings` entry replaces its `maxEffortLevel`. */
88
+ function sourceCap(settings: Record<string, unknown>, model: string | undefined): unknown {
89
+ const perModel = settings.modelSettings;
90
+ if (model && perModel && typeof perModel === "object") {
91
+ const key = modelKey(model);
92
+ let lowest: EffortLevel | undefined;
93
+ for (const [name, entry] of Object.entries(perModel as Record<string, unknown>)) {
94
+ const level = (entry as { maxEffortLevel?: unknown } | null)?.maxEffortLevel;
95
+ if (modelKey(name) !== key || !isEffortLevel(level)) continue;
96
+ if (!lowest || effortRank(level) < effortRank(lowest)) lowest = level;
97
+ }
98
+ if (lowest) return lowest;
99
+ }
100
+ return settings.maxEffortLevel;
101
+ }
102
+
103
+ /**
104
+ * The overrides the settings and the environment hold: the `CLAUDE_CODE_EFFORT_LEVEL` that wins
105
+ * (the highest-priority settings file that sets it, else the environment, which every settings
106
+ * file outranks) and the lowest cap (across files the lowest wins, and "max" caps nothing).
107
+ */
108
+ export function effortOverrides(p: {
109
+ sources: ReadonlyArray<CliSettingsSource | null | undefined>;
110
+ processEnv?: string;
111
+ model?: string;
112
+ userFile: string;
113
+ }): Pick<EffortStatus, "env" | "cap"> {
114
+ const out: Pick<EffortStatus, "env" | "cap"> = {};
115
+ const sources = p.sources.filter((s): s is CliSettingsSource => !!s?.settings && typeof s.settings === "object");
116
+
117
+ let env: EffortSetting<string> | undefined;
118
+ for (let i = sources.length - 1; i >= 0 && !env; i--) {
119
+ const value = (sources[i]!.settings.env as Record<string, unknown> | undefined)?.[EFFORT_ENV_VAR];
120
+ if (typeof value === "string") env = { value, ...place(sources[i]!.source, p.userFile) };
121
+ }
122
+ env ??= p.processEnv !== undefined ? { value: p.processEnv, source: "environment" } : undefined;
123
+ // A value the CLI ignores overrides nothing — but it still hides a lower one, as it does there.
124
+ if (env && effortEnvMeaning(env.value)) out.env = env;
125
+
126
+ for (const s of sources) {
127
+ const level = sourceCap(s.settings, p.model);
128
+ if (!isEffortLevel(level) || level === "max") continue;
129
+ if (!out.cap || effortRank(level) < effortRank(out.cap.value)) out.cap = { value: level, ...place(s.source, p.userFile) };
130
+ }
131
+ return out;
132
+ }
133
+
134
+ function levels(values: unknown): EffortLevel[] {
135
+ return Array.isArray(values) ? values.filter(isEffortLevel) : [];
136
+ }
137
+
138
+ /** The model's levels, from the `initialize` answer; undefined when it lists no such model. */
139
+ export function supportedLevels(models: CliModelInfo[] | undefined, model: string | undefined): EffortLevel[] | undefined {
140
+ if (!models || !model) return undefined;
141
+ const bare = (name: string) => name.toLowerCase().replace(/\[[^\]]*\]$/, "");
142
+ const hit = models.find((m) => [m.value, m.resolvedModel].some((name) => name && bare(name) === bare(model)));
143
+ return hit ? levels(hit.supportedEffortLevels) : undefined;
144
+ }
145
+
146
+ /** The running CLI's answer, as a status: its overrides plus what it said it sends. */
147
+ export function effortStatusFromCli(p: {
148
+ answer: CliSettingsAnswer;
149
+ models?: CliModelInfo[];
150
+ requested: string | null | undefined;
151
+ processEnv?: string;
152
+ userFile: string;
153
+ }): EffortStatus {
154
+ const model = typeof p.answer.applied?.model === "string" ? p.answer.applied.model : undefined;
155
+ const applied = p.answer.applied?.effort;
156
+ const supported = supportedLevels(p.models, model);
157
+ return {
158
+ ...effortOverrides({ sources: p.answer.sources ?? [], processEnv: p.processEnv, model, userFile: p.userFile }),
159
+ observed: {
160
+ requested: isEffortLevel(p.requested) ? p.requested : null,
161
+ applied: isEffortLevel(applied) ? applied : null,
162
+ ...(model && { model }),
163
+ },
164
+ ...(supported && { supported }),
165
+ };
166
+ }
167
+
168
+ /**
169
+ * The settings files a chat in `projectPath` reads, lowest priority first: those of the sources
170
+ * its CLI is started with, so the estimate never counts a file the chat does not load. A file
171
+ * that does not parse is skipped, as the CLI skips it.
172
+ */
173
+ export function readSettingsFiles(
174
+ projectPath: string | undefined,
175
+ userPath: string,
176
+ sources: readonly ClaudeSettingSource[] = CLAUDE_SETTING_SOURCES,
177
+ ): CliSettingsSource[] {
178
+ const fileOf: Record<ClaudeSettingSource, [EffortSettingSource, string] | null> = {
179
+ user: ["userSettings", userPath],
180
+ project: projectPath ? ["projectSettings", join(projectPath, ".claude", "settings.json")] : null,
181
+ local: projectPath ? ["localSettings", join(projectPath, ".claude", "settings.local.json")] : null,
182
+ };
183
+ const files = sources.map((source) => fileOf[source]).filter((f): f is [EffortSettingSource, string] => !!f);
184
+ const out: CliSettingsSource[] = [];
185
+ for (const [source, file] of files) {
186
+ let settings: unknown;
187
+ try { settings = JSON.parse(readFileSync(file, "utf8")); } catch { continue; }
188
+ if (settings && typeof settings === "object" && !Array.isArray(settings)) out.push({ source, settings: settings as Record<string, unknown> });
189
+ }
190
+ return out;
191
+ }
192
+
193
+ /**
194
+ * The overrides a chat in `projectPath` will meet, from the files alone — for a session no CLI
195
+ * has answered for yet. Managed settings and organization limits are left to the CLI's answer.
196
+ */
197
+ export function estimateEffortStatus(p: {
198
+ projectPath?: string;
199
+ model?: string;
200
+ env?: Record<string, string | undefined>;
201
+ home?: string;
202
+ }): EffortStatus {
203
+ const env = p.env ?? process.env;
204
+ const user = userSettingsFile(env, p.home);
205
+ return effortOverrides({
206
+ sources: readSettingsFiles(p.projectPath, user.path),
207
+ processEnv: env[EFFORT_ENV_VAR],
208
+ model: p.model,
209
+ userFile: user.shown,
210
+ });
211
+ }
@@ -5,6 +5,7 @@ import { configService } from "../services/config.service.ts";
5
5
  import { nextProbeDelayMs, type ProviderProbeStatus } from "./provider-probe.ts";
6
6
  import { CODEX_DEFAULT_MODEL } from "../types/config.ts";
7
7
  import { createLogger } from "../services/logger.ts";
8
+ import { registerMemoryGauge } from "../services/memory-diagnostics.ts";
8
9
 
9
10
  const log = createLogger("registry");
10
11
 
@@ -53,7 +54,13 @@ class ProviderRegistry {
53
54
  export const providerRegistry = new ProviderRegistry();
54
55
 
55
56
  // SDK providers registered synchronously (no binary check needed)
56
- providerRegistry.register(new ClaudeAgentSdkProvider());
57
+ const claudeProvider = new ClaudeAgentSdkProvider();
58
+ providerRegistry.register(claudeProvider);
59
+ // Sizes for the opt-in memory monitor (PPM_MEM_DIAG). Bracket access reaches the private
60
+ // maps without widening the class; registering costs nothing while the monitor is off.
61
+ registerMemoryGauge("claude.streamingSessions", () => claudeProvider["streamingSessions"].size);
62
+ registerMemoryGauge("claude.activeQueries", () => claudeProvider["activeQueries"].size);
63
+ registerMemoryGauge("claude.activeSessions", () => claudeProvider["activeSessions"].size);
57
64
  providerRegistry.register(new MockProvider()); // testing only
58
65
 
59
66
  /**
@@ -176,6 +183,8 @@ async function probeCodexProvider(): Promise<ProviderProbeStatus> {
176
183
  const result = await codex.probe();
177
184
  if (result.ok) {
178
185
  providerRegistry.register(codex);
186
+ registerMemoryGauge("codex.sessions", () => codex["sessions"].size);
187
+ registerMemoryGauge("codex.live", () => codex["live"].size);
179
188
  ensureCodexConfigEntry();
180
189
  log.info("Codex provider registered (@openai/codex found)");
181
190
  status = { id: "codex", registered: true, attempts, lastProbeAt };
@@ -1,5 +1,4 @@
1
1
  import { Hono } from "hono";
2
- import { cors } from "hono/cors";
3
2
  import { writeFileSync } from "node:fs";
4
3
  import { SERVER_PORT_FILE } from "../services/edge-target-resolver.ts";
5
4
  import { configService } from "../services/config.service.ts";
@@ -7,6 +6,7 @@ import { VERSION } from "../version.ts";
7
6
  import { authMiddleware } from "./middleware/auth.ts";
8
7
  import { gzipJson } from "./middleware/gzip-json.ts";
9
8
  import { accessLog } from "./middleware/access-log.ts";
9
+ import { corsHeaders } from "./middleware/cors-headers.ts";
10
10
  import { createLogger } from "../services/logger.ts";
11
11
  import { projectRoutes } from "./routes/projects.ts";
12
12
  import { settingsRoutes } from "./routes/settings.ts";
@@ -167,14 +167,15 @@ app.onError((error, c) => {
167
167
  return c.text("Internal Server Error", 500);
168
168
  });
169
169
 
170
- // Design share links: the public reviewer surface. Ahead of `cors()` (which would answer `*`
171
- // and re-wrap range bodies), of `/api` auth (a share code is never the PPM token) and of the
172
- // static fallback (its catch-all keeps every `/s` path off the app shell).
170
+ // Design share links: the public reviewer surface. Ahead of the CORS layer, of `/api` auth
171
+ // (a share code is never the PPM token) and of the static fallback (its catch-all keeps every
172
+ // `/s` path off the app shell).
173
173
  import { designShareGuestRoutes } from "./routes/design-share-guest.ts";
174
174
  app.route("/s", designShareGuestRoutes);
175
175
 
176
- // CORS for dev
177
- app.use("*", cors());
176
+ // CORS for dev. Not Hono's `cors()`: it writes headers before the handler, which makes Hono
177
+ // rebuild every response from its body and turns a `Bun.file` body into a leaking stream.
178
+ app.use("*", corsHeaders);
178
179
 
179
180
  // Public endpoints (before auth)
180
181
  // Per-boot id so a named-tunnel health probe can tell "my connector is dark"
@@ -13,6 +13,7 @@
13
13
  */
14
14
  import type { MiddlewareHandler } from "hono";
15
15
  import { createLogger, type LogLevel } from "../../services/logger.ts";
16
+ import { requestTracker } from "../../services/memory-diagnostics-requests.ts";
16
17
 
17
18
  const log = createLogger("http");
18
19
 
@@ -119,6 +120,8 @@ export const accessLog: MiddlewareHandler = async (c, next) => {
119
120
  const ms = Math.round(performance.now() - started);
120
121
  const url = new URL(c.req.url);
121
122
  const status = c.res.status;
123
+ // Before the level check: the memory monitor counts every request, logged or not.
124
+ requestTracker.record(c.req.method, url.pathname, c.res.headers.get("content-length"));
122
125
  const level = accessLogLevel(c.req.method, url.pathname, status, ms);
123
126
  if (!log.isEnabled(level)) return;
124
127
  const line = `${c.req.method} ${describeRequestTarget(url)} ${status} ${ms}ms${ms >= SLOW_REQUEST_MS ? " (slow)" : ""}`;
@@ -0,0 +1,44 @@
1
+ import type { MiddlewareHandler } from "hono";
2
+
3
+ /**
4
+ * The app-wide CORS headers, written onto the handler's own response AFTER it is made.
5
+ *
6
+ * Hono's `cors()` sets its headers on `c.res` before `next()`, which makes Hono build a
7
+ * placeholder response; when the handler then returns its own, the `c.res` setter rebuilds it
8
+ * as `new Response(res.body, res)` to merge the two. Reading `.body` turns a `Bun.file(...)`
9
+ * body into Bun's file stream — the one that keeps about the file's size in native memory per
10
+ * response and never returns it (see `services/file-chunks.ts`). Measured on Bun 1.3.10
11
+ * (Windows), a 23 MB file: 2.9 MB per response bare, 30.2 MB behind `cors()`, and the
12
+ * response lost its `Content-Length` (sent chunked). Every static asset, chat image and
13
+ * download went through that.
14
+ *
15
+ * Same answer as `cors()` with its defaults (`origin: "*"`), with one difference: a route that
16
+ * sets `Access-Control-Allow-Origin` itself keeps it (`cors()` overwrote it, which is why the
17
+ * design preview had to re-assert `null`).
18
+ */
19
+ const ALLOW_METHODS = "GET,HEAD,PUT,POST,DELETE,PATCH";
20
+
21
+ export const corsHeaders: MiddlewareHandler = async (c, next) => {
22
+ if (c.req.method === "OPTIONS") {
23
+ // A preflight never reaches a handler, so answering it here rebuilds nothing.
24
+ const headers = new Headers({ "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": ALLOW_METHODS });
25
+ const requested = c.req.header("Access-Control-Request-Headers");
26
+ if (requested) {
27
+ headers.set("Access-Control-Allow-Headers", requested.split(/\s*,\s*/).join(","));
28
+ headers.append("Vary", "Access-Control-Request-Headers");
29
+ }
30
+ return new Response(null, { status: 204, statusText: "No Content", headers });
31
+ }
32
+
33
+ await next();
34
+ if (c.res.headers.has("Access-Control-Allow-Origin")) return;
35
+ try {
36
+ c.res.headers.set("Access-Control-Allow-Origin", "*");
37
+ } catch {
38
+ // A response whose headers are immutable (one passed through from `fetch`) is rebuilt;
39
+ // its body is a stream already, so there is no file to turn into one.
40
+ const res = new Response(c.res.body, c.res);
41
+ res.headers.set("Access-Control-Allow-Origin", "*");
42
+ c.res = res;
43
+ }
44
+ };
@@ -166,6 +166,23 @@ chatRoutes.get("/providers/:providerId/models", async (c) => {
166
166
  }
167
167
  });
168
168
 
169
+ /**
170
+ * GET /chat/providers/:providerId/effort-status?model= — what overrides the effort picked for a
171
+ * new chat in this project (`CLAUDE_CODE_EFFORT_LEVEL`, a `maxEffortLevel` cap), from the settings
172
+ * files; a session's own socket carries the CLI's answer once a turn has run. `status` is null
173
+ * for a provider nothing overrides.
174
+ */
175
+ chatRoutes.get("/providers/:providerId/effort-status", (c) => {
176
+ const provider = providerRegistry.get(c.req.param("providerId"));
177
+ if (!provider) return c.json(err(`Provider "${c.req.param("providerId")}" not found`), 404);
178
+ try {
179
+ const status = provider.estimateEffortStatus?.(c.get("projectPath"), c.req.query("model") || undefined) ?? null;
180
+ return c.json(ok({ status }));
181
+ } catch (e) {
182
+ return c.json(err((e as Error).message), 500);
183
+ }
184
+ });
185
+
169
186
  /** GET /chat/sessions — list chat sessions filtered by project from context */
170
187
  chatRoutes.get("/sessions", async (c) => {
171
188
  try {
@@ -1277,7 +1294,10 @@ chatRoutes.get("/uploads/:filename", async (c) => {
1277
1294
  if (c.req.header("if-none-match") === etag) {
1278
1295
  return new Response(null, { status: 304, headers: { ETag: etag } });
1279
1296
  }
1280
- return new Response(file.stream(), {
1297
+ // The file itself, not `file.stream()`: that stream keeps about the file's size in native
1298
+ // memory per response and never returns it (see `file-chunks.ts`), and chat images are
1299
+ // served every time a tab opens without them cached.
1300
+ return new Response(file, {
1281
1301
  headers: {
1282
1302
  "Content-Type": file.type || "application/octet-stream",
1283
1303
  "Cache-Control": "public, max-age=31536000, immutable",
@@ -310,7 +310,7 @@ impExpJobRoutes.post("/jobs/:id/download", async (c) => {
310
310
  const ticket = issueExportTicket({
311
311
  fileName: file.name,
312
312
  contentType: file.format === "zip" ? "application/zip" : CONTENT_TYPES[file.format],
313
- open: () => Bun.file(file.path).stream(),
313
+ open: () => Bun.file(file.path),
314
314
  abandon: () => {},
315
315
  });
316
316
  return c.json(ok<GridExportTicket>({ ticket, fileName: file.name }));