@hienlh/ppm 0.23.22 → 0.23.24

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 (463) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/CLAUDE.md +3 -1
  3. package/README.md +1 -1
  4. package/assets/skills/ppm/SKILL.md +1 -1
  5. package/assets/skills/ppm/references/http-api.md +2 -1
  6. package/dist/web/assets/{accounts-settings-section-CiNmIj02.js → accounts-settings-section-Cg-LncY6.js} +1 -1
  7. package/dist/web/assets/accounts-settings-section-Cg-LncY6.js.br +0 -0
  8. package/dist/web/assets/accounts-settings-section-Cg-LncY6.js.gz +0 -0
  9. package/dist/web/assets/{agent-session-sheet-DRR36PDA.js → agent-session-sheet-DMdV8GOu.js} +2 -2
  10. package/dist/web/assets/agent-session-sheet-DMdV8GOu.js.br +0 -0
  11. package/dist/web/assets/agent-session-sheet-DMdV8GOu.js.gz +0 -0
  12. package/dist/web/assets/{agent-session-window-content-DMhy16BM.js → agent-session-window-content-CYg58J-V.js} +1 -1
  13. package/dist/web/assets/agent-session-window-content-CYg58J-V.js.br +0 -0
  14. package/dist/web/assets/agent-session-window-content-CYg58J-V.js.gz +0 -0
  15. package/dist/web/assets/{ai-resource-editor-BG8boPw7.js → ai-resource-editor-BvEnn0Z_.js} +1 -1
  16. package/dist/web/assets/ai-resource-editor-BvEnn0Z_.js.br +0 -0
  17. package/dist/web/assets/ai-resource-editor-BvEnn0Z_.js.gz +0 -0
  18. package/dist/web/assets/{ai-settings-section-De5dYWlC.js → ai-settings-section-Bo-nf2rY.js} +1 -1
  19. package/dist/web/assets/ai-settings-section-DAqiu_11.js +1 -0
  20. package/dist/web/assets/ai-settings-section-DAqiu_11.js.br +0 -0
  21. package/dist/web/assets/ai-settings-section-DAqiu_11.js.gz +0 -0
  22. package/dist/web/assets/{android-tab-lHDxD93G.js → android-tab-C-wmlFc0.js} +1 -1
  23. package/dist/web/assets/android-tab-C-wmlFc0.js.br +0 -0
  24. package/dist/web/assets/android-tab-C-wmlFc0.js.gz +0 -0
  25. package/dist/web/assets/{appearance-settings-section-DO-ZyoMj.js → appearance-settings-section-CYqX6BGh.js} +1 -1
  26. package/dist/web/assets/appearance-settings-section-CYqX6BGh.js.br +0 -0
  27. package/dist/web/assets/appearance-settings-section-CYqX6BGh.js.gz +0 -0
  28. package/dist/web/assets/{apps-panel-CH-0CHYY.js → apps-panel-D1VTBxgC.js} +1 -1
  29. package/dist/web/assets/apps-panel-D1VTBxgC.js.br +0 -0
  30. package/dist/web/assets/apps-panel-D1VTBxgC.js.gz +0 -0
  31. package/dist/web/assets/{branch-review-tab-CdGtvekz.js → branch-review-tab-BG6vxSsC.js} +1 -1
  32. package/dist/web/assets/branch-review-tab-BG6vxSsC.js.br +0 -0
  33. package/dist/web/assets/branch-review-tab-BG6vxSsC.js.gz +0 -0
  34. package/dist/web/assets/chat-tab-BAwGLGyR.js +19 -0
  35. package/dist/web/assets/chat-tab-BAwGLGyR.js.br +0 -0
  36. package/dist/web/assets/chat-tab-BAwGLGyR.js.gz +0 -0
  37. package/dist/web/assets/{chat-tab-DMqWnFIo.js → chat-tab-BJ0Mi2sd.js} +1 -1
  38. package/dist/web/assets/chat-tab-BJ0Mi2sd.js.br +0 -0
  39. package/dist/web/assets/chat-tab-BJ0Mi2sd.js.gz +0 -0
  40. package/dist/web/assets/{code-editor-BCv8ONNS.js → code-editor-DuwbwTpM.js} +4 -4
  41. package/dist/web/assets/code-editor-DuwbwTpM.js.br +0 -0
  42. package/dist/web/assets/code-editor-DuwbwTpM.js.gz +0 -0
  43. package/dist/web/assets/{codex-reset-credit-button-CGMbIHak.js → codex-reset-credit-button-tIwfnf2G.js} +1 -1
  44. package/dist/web/assets/codex-reset-credit-button-tIwfnf2G.js.br +0 -0
  45. package/dist/web/assets/{codex-reset-credit-button-CGMbIHak.js.gz → codex-reset-credit-button-tIwfnf2G.js.gz} +0 -0
  46. package/dist/web/assets/{conflict-editor-3WmzqBlW.js → conflict-editor-D2n-hQqs.js} +1 -1
  47. package/dist/web/assets/conflict-editor-D2n-hQqs.js.br +0 -0
  48. package/dist/web/assets/conflict-editor-D2n-hQqs.js.gz +0 -0
  49. package/dist/web/assets/{connection-form-tab-BoWucvAe.js → connection-form-tab-DYqPNq_-.js} +1 -1
  50. package/dist/web/assets/connection-form-tab-DYqPNq_-.js.br +0 -0
  51. package/dist/web/assets/connection-form-tab-DYqPNq_-.js.gz +0 -0
  52. package/dist/web/assets/{csv-preview-BCgGsy7F.js → csv-preview-Xh3Z6BS5.js} +1 -1
  53. package/dist/web/assets/csv-preview-Xh3Z6BS5.js.br +0 -0
  54. package/dist/web/assets/csv-preview-Xh3Z6BS5.js.gz +0 -0
  55. package/dist/web/assets/{db-tab-parts-o6QMHBbA.js → db-tab-parts-Dqnb3GCP.js} +1 -1
  56. package/dist/web/assets/db-tab-parts-Dqnb3GCP.js.br +0 -0
  57. package/dist/web/assets/db-tab-parts-Dqnb3GCP.js.gz +0 -0
  58. package/dist/web/assets/{design-settings-section-seo6SdSk.js → design-settings-section-DNNWgxF9.js} +1 -1
  59. package/dist/web/assets/design-settings-section-DNNWgxF9.js.br +0 -0
  60. package/dist/web/assets/design-settings-section-DNNWgxF9.js.gz +0 -0
  61. package/dist/web/assets/{design-tab-B-f_b2LM.js → design-tab-BoAdSpZZ.js} +1 -1
  62. package/dist/web/assets/design-tab-BoAdSpZZ.js.br +0 -0
  63. package/dist/web/assets/design-tab-BoAdSpZZ.js.gz +0 -0
  64. package/dist/web/assets/{designs-sidebar-panel-CVT_TQP7.js → designs-sidebar-panel-BKw7HFAz.js} +1 -1
  65. package/dist/web/assets/designs-sidebar-panel-BKw7HFAz.js.br +0 -0
  66. package/dist/web/assets/designs-sidebar-panel-BKw7HFAz.js.gz +0 -0
  67. package/dist/web/assets/{diff-viewer-y9uBp_8u.js → diff-viewer-CRmfCAZX.js} +1 -1
  68. package/dist/web/assets/diff-viewer-CRmfCAZX.js.br +0 -0
  69. package/dist/web/assets/diff-viewer-CRmfCAZX.js.gz +0 -0
  70. package/dist/web/assets/{docx-preview-Dn_x3Inm.js → docx-preview-zIrtL09u.js} +1 -1
  71. package/dist/web/assets/docx-preview-zIrtL09u.js.br +0 -0
  72. package/dist/web/assets/docx-preview-zIrtL09u.js.gz +0 -0
  73. package/dist/web/assets/{editor-dialog-BwNXSajD.js → editor-dialog-jMYvXVla.js} +1 -1
  74. package/dist/web/assets/editor-dialog-jMYvXVla.js.br +0 -0
  75. package/dist/web/assets/editor-dialog-jMYvXVla.js.gz +0 -0
  76. package/dist/web/assets/{editor-language-service-XA_TnPLN.js → editor-language-service-Yb-61Bro.js} +1 -1
  77. package/dist/web/assets/editor-language-service-Yb-61Bro.js.br +0 -0
  78. package/dist/web/assets/editor-language-service-Yb-61Bro.js.gz +0 -0
  79. package/dist/web/assets/{explorer-body-DukR7zM9.js → explorer-body-C6qKehmd.js} +1 -1
  80. package/dist/web/assets/explorer-body-C6qKehmd.js.br +0 -0
  81. package/dist/web/assets/explorer-body-C6qKehmd.js.gz +0 -0
  82. package/dist/web/assets/{explorer-window-content-BjTZ44S3.js → explorer-window-content-B9P8grrY.js} +1 -1
  83. package/dist/web/assets/explorer-window-content-B9P8grrY.js.br +0 -0
  84. package/dist/web/assets/explorer-window-content-B9P8grrY.js.gz +0 -0
  85. package/dist/web/assets/{extension-manager-section-C4WD1BPQ.js → extension-manager-section-C45v1QtG.js} +1 -1
  86. package/dist/web/assets/extension-manager-section-C45v1QtG.js.br +0 -0
  87. package/dist/web/assets/extension-manager-section-C45v1QtG.js.gz +0 -0
  88. package/dist/web/assets/{extension-webview-CV2ujflc.js → extension-webview-DIxM-xkH.js} +1 -1
  89. package/dist/web/assets/extension-webview-DIxM-xkH.js.br +0 -0
  90. package/dist/web/assets/extension-webview-DIxM-xkH.js.gz +0 -0
  91. package/dist/web/assets/{files-settings-section-hS5ixQ8o.js → files-settings-section-DeVlzKXd.js} +1 -1
  92. package/dist/web/assets/files-settings-section-DeVlzKXd.js.br +0 -0
  93. package/dist/web/assets/files-settings-section-DeVlzKXd.js.gz +0 -0
  94. package/dist/web/assets/format-date-CkyHzZLA.js +1 -0
  95. package/dist/web/assets/{general-settings-section-BnKYY0AA.js → general-settings-section-CLVDIEym.js} +1 -1
  96. package/dist/web/assets/general-settings-section-CLVDIEym.js.br +0 -0
  97. package/dist/web/assets/general-settings-section-CLVDIEym.js.gz +0 -0
  98. package/dist/web/assets/{git-log-panel-JiWfR5xv.js → git-log-panel-D3Q9NRll.js} +1 -1
  99. package/dist/web/assets/git-log-panel-D3Q9NRll.js.br +0 -0
  100. package/dist/web/assets/git-log-panel-D3Q9NRll.js.gz +0 -0
  101. package/dist/web/assets/{git-review-tab-DgvcY__Q.js → git-review-tab-ORgiYbva.js} +1 -1
  102. package/dist/web/assets/git-review-tab-ORgiYbva.js.br +0 -0
  103. package/dist/web/assets/git-review-tab-ORgiYbva.js.gz +0 -0
  104. package/dist/web/assets/{glide-data-grid-r1MKxDjz.js → glide-data-grid-CtWl1BTk.js} +1 -1
  105. package/dist/web/assets/glide-data-grid-CtWl1BTk.js.br +0 -0
  106. package/dist/web/assets/glide-data-grid-CtWl1BTk.js.gz +0 -0
  107. package/dist/web/assets/{grid-save-dialog-Fa0yA-Ep.js → grid-save-dialog-eV25Mqpg.js} +1 -1
  108. package/dist/web/assets/grid-save-dialog-eV25Mqpg.js.br +0 -0
  109. package/dist/web/assets/grid-save-dialog-eV25Mqpg.js.gz +0 -0
  110. package/dist/web/assets/{group-chat-tab-B2Q8yqSX.js → group-chat-tab-CYmbYXN2.js} +1 -1
  111. package/dist/web/assets/group-chat-tab-CYmbYXN2.js.br +0 -0
  112. package/dist/web/assets/group-chat-tab-CYmbYXN2.js.gz +0 -0
  113. package/dist/web/assets/{impexp-tab-B8Ht4l32.js → impexp-tab-D2OKxpFq.js} +1 -1
  114. package/dist/web/assets/impexp-tab-D2OKxpFq.js.br +0 -0
  115. package/dist/web/assets/impexp-tab-D2OKxpFq.js.gz +0 -0
  116. package/dist/web/assets/{index-BtAn_krv.js → index-D8QDpdgs.js} +7 -7
  117. package/dist/web/assets/index-D8QDpdgs.js.br +0 -0
  118. package/dist/web/assets/index-D8QDpdgs.js.gz +0 -0
  119. package/dist/web/assets/{jira-watcher-section--ibKZfBs.js → jira-watcher-section-B3OaMlim.js} +1 -1
  120. package/dist/web/assets/{keybindings-store-Cwxl4tHv.js → keybindings-store-C_aRhBu4.js} +1 -1
  121. package/dist/web/assets/{keyboard-shortcuts-section-aoiLpWwf.js → keyboard-shortcuts-section-DXRyKrSy.js} +1 -1
  122. package/dist/web/assets/keyboard-shortcuts-section-DXRyKrSy.js.br +0 -0
  123. package/dist/web/assets/keyboard-shortcuts-section-DXRyKrSy.js.gz +0 -0
  124. package/dist/web/assets/{language-servers-section-BkGAezo6.js → language-servers-section-BTs9iuZc.js} +2 -2
  125. package/dist/web/assets/language-servers-section-BTs9iuZc.js.br +0 -0
  126. package/dist/web/assets/language-servers-section-BTs9iuZc.js.gz +0 -0
  127. package/dist/web/assets/{logs-app-DYgPtmjm.js → logs-app-BlhVTOYN.js} +1 -1
  128. package/dist/web/assets/logs-app-BlhVTOYN.js.br +0 -0
  129. package/dist/web/assets/logs-app-BlhVTOYN.js.gz +0 -0
  130. package/dist/web/assets/{logs-tab-BbDosK63.js → logs-tab-kqNARbz0.js} +1 -1
  131. package/dist/web/assets/{logs-window-content-CEXAyv-f.js → logs-window-content-UUCJJCqG.js} +1 -1
  132. package/dist/web/assets/{lsp-client-CIcphNmU.js → lsp-client-BTlK_8lb.js} +1 -1
  133. package/dist/web/assets/lsp-client-BTlK_8lb.js.br +0 -0
  134. package/dist/web/assets/lsp-client-BTlK_8lb.js.gz +0 -0
  135. package/dist/web/assets/{markdown-renderer-BNzQeoAO.js → markdown-renderer-ClGxDien.js} +1 -1
  136. package/dist/web/assets/{markdown-renderer-dk1w3goH.js → markdown-renderer-DpsToFFB.js} +1 -1
  137. package/dist/web/assets/markdown-renderer-DpsToFFB.js.br +0 -0
  138. package/dist/web/assets/markdown-renderer-DpsToFFB.js.gz +0 -0
  139. package/dist/web/assets/{mobile-explorer-sheet-D4lPZQZc.js → mobile-explorer-sheet-CybJbScN.js} +1 -1
  140. package/dist/web/assets/mobile-explorer-sheet-CybJbScN.js.br +0 -0
  141. package/dist/web/assets/mobile-explorer-sheet-CybJbScN.js.gz +0 -0
  142. package/dist/web/assets/{new-design-dialog-CIq7xspW.js → new-design-dialog-9WTgcJoj.js} +1 -1
  143. package/dist/web/assets/new-design-dialog-9WTgcJoj.js.br +0 -0
  144. package/dist/web/assets/new-design-dialog-9WTgcJoj.js.gz +0 -0
  145. package/dist/web/assets/{notification-store-Rv36noYX.js → notification-store-CPVq5ec5.js} +1 -1
  146. package/dist/web/assets/{notifications-settings-section-CoWMtXCC.js → notifications-settings-section-xrYII-pL.js} +1 -1
  147. package/dist/web/assets/notifications-settings-section-xrYII-pL.js.br +0 -0
  148. package/dist/web/assets/notifications-settings-section-xrYII-pL.js.gz +0 -0
  149. package/dist/web/assets/{ppmbot-settings-section-Ct0UwA6T.js → ppmbot-settings-section-Bc46iDP9.js} +1 -1
  150. package/dist/web/assets/ppmbot-settings-section-Bc46iDP9.js.br +0 -0
  151. package/dist/web/assets/ppmbot-settings-section-Bc46iDP9.js.gz +0 -0
  152. package/dist/web/assets/{problems-panel-D-HT-0PE.js → problems-panel-D49-Xkzd.js} +1 -1
  153. package/dist/web/assets/problems-panel-D49-Xkzd.js.br +0 -0
  154. package/dist/web/assets/problems-panel-D49-Xkzd.js.gz +0 -0
  155. package/dist/web/assets/{process-table-CXN0s4Pq.js → process-table-NxYTZ0n9.js} +1 -1
  156. package/dist/web/assets/process-table-NxYTZ0n9.js.br +0 -0
  157. package/dist/web/assets/process-table-NxYTZ0n9.js.gz +0 -0
  158. package/dist/web/assets/{proxy-settings-section-e6WWzP8n.js → proxy-settings-section-Exx7cQc4.js} +1 -1
  159. package/dist/web/assets/proxy-settings-section-Exx7cQc4.js.br +0 -0
  160. package/dist/web/assets/proxy-settings-section-Exx7cQc4.js.gz +0 -0
  161. package/dist/web/assets/{query-tab-C7C67t8F.js → query-tab-BWtJPuxw.js} +1 -1
  162. package/dist/web/assets/query-tab-BWtJPuxw.js.br +0 -0
  163. package/dist/web/assets/query-tab-BWtJPuxw.js.gz +0 -0
  164. package/dist/web/assets/{remote-access-settings-section-gGoATCF9.js → remote-access-settings-section-C4K__DNW.js} +1 -1
  165. package/dist/web/assets/remote-access-settings-section-C4K__DNW.js.br +0 -0
  166. package/dist/web/assets/remote-access-settings-section-C4K__DNW.js.gz +0 -0
  167. package/dist/web/assets/{remote-desktop-clipboard-notice-D5KjvMZx.js → remote-desktop-clipboard-notice-DQyysBwL.js} +1 -1
  168. package/dist/web/assets/remote-desktop-clipboard-notice-DQyysBwL.js.br +0 -0
  169. package/dist/web/assets/remote-desktop-clipboard-notice-DQyysBwL.js.gz +0 -0
  170. package/dist/web/assets/{remote-desktop-mobile-sheet-CY_DsGXZ.js → remote-desktop-mobile-sheet-DZtEFmgf.js} +2 -2
  171. package/dist/web/assets/remote-desktop-mobile-sheet-DZtEFmgf.js.br +0 -0
  172. package/dist/web/assets/remote-desktop-mobile-sheet-DZtEFmgf.js.gz +0 -0
  173. package/dist/web/assets/{remote-desktop-mobile-view-DdpI-RF8.js → remote-desktop-mobile-view-VW7HdPUX.js} +1 -1
  174. package/dist/web/assets/remote-desktop-mobile-view-VW7HdPUX.js.br +0 -0
  175. package/dist/web/assets/{remote-desktop-mobile-view-DdpI-RF8.js.gz → remote-desktop-mobile-view-VW7HdPUX.js.gz} +0 -0
  176. package/dist/web/assets/{remote-desktop-readiness-gate-WIyCCAv5.js → remote-desktop-readiness-gate-B3tuP9Kt.js} +1 -1
  177. package/dist/web/assets/remote-desktop-readiness-gate-B3tuP9Kt.js.br +0 -0
  178. package/dist/web/assets/remote-desktop-readiness-gate-B3tuP9Kt.js.gz +0 -0
  179. package/dist/web/assets/{remote-desktop-window-content-BMORZLZt.js → remote-desktop-window-content-n0NoDCaE.js} +1 -1
  180. package/dist/web/assets/remote-desktop-window-content-n0NoDCaE.js.br +0 -0
  181. package/dist/web/assets/remote-desktop-window-content-n0NoDCaE.js.gz +0 -0
  182. package/dist/web/assets/{rename-object-dialog-pIgWe_AL.js → rename-object-dialog-rLTW4A55.js} +1 -1
  183. package/dist/web/assets/rename-object-dialog-rLTW4A55.js.br +0 -0
  184. package/dist/web/assets/rename-object-dialog-rLTW4A55.js.gz +0 -0
  185. package/dist/web/assets/{review-code-oTNcTYlI.js → review-code-DNfsaNO1.js} +1 -1
  186. package/dist/web/assets/review-code-DNfsaNO1.js.br +0 -0
  187. package/dist/web/assets/review-code-DNfsaNO1.js.gz +0 -0
  188. package/dist/web/assets/{save-as-dialog-CnPLGTaL.js → save-as-dialog-BSz8WrNZ.js} +1 -1
  189. package/dist/web/assets/save-as-dialog-BSz8WrNZ.js.br +0 -0
  190. package/dist/web/assets/save-as-dialog-BSz8WrNZ.js.gz +0 -0
  191. package/dist/web/assets/{schedules-settings-section-CREqHG_i.js → schedules-settings-section-DqUZ-H2K.js} +1 -1
  192. package/dist/web/assets/schedules-settings-section-DqUZ-H2K.js.br +0 -0
  193. package/dist/web/assets/schedules-settings-section-DqUZ-H2K.js.gz +0 -0
  194. package/dist/web/assets/{session-review-tab-nFVJYYJH.js → session-review-tab-DKtrFb9y.js} +1 -1
  195. package/dist/web/assets/session-review-tab-DKtrFb9y.js.br +0 -0
  196. package/dist/web/assets/session-review-tab-DKtrFb9y.js.gz +0 -0
  197. package/dist/web/assets/{settings-body-DNzH0wOQ.js → settings-body-C42xA9Dw.js} +1 -1
  198. package/dist/web/assets/settings-body-C42xA9Dw.js.br +0 -0
  199. package/dist/web/assets/settings-body-C42xA9Dw.js.gz +0 -0
  200. package/dist/web/assets/{settings-rows-D02_4sDY.js → settings-rows-C7ZatT9O.js} +1 -1
  201. package/dist/web/assets/settings-rows-C7ZatT9O.js.br +0 -0
  202. package/dist/web/assets/settings-rows-C7ZatT9O.js.gz +0 -0
  203. package/dist/web/assets/{settings-section-content-DWopMfmq.js → settings-section-content-B_iXJpcK.js} +2 -2
  204. package/dist/web/assets/settings-section-content-B_iXJpcK.js.br +0 -0
  205. package/dist/web/assets/settings-section-content-B_iXJpcK.js.gz +0 -0
  206. package/dist/web/assets/settings-section-content-rxfQEQ66.js +1 -0
  207. package/dist/web/assets/{settings-tab-Cg0v5kO7.js → settings-tab-PRw6OpEb.js} +1 -1
  208. package/dist/web/assets/{settings-window-content-CqhwxJJn.js → settings-window-content-WITQsGfW.js} +1 -1
  209. package/dist/web/assets/{slash-command-picker-Bcizxvbh.js → slash-command-picker-DmDs8nOj.js} +1 -1
  210. package/dist/web/assets/slash-command-picker-DmDs8nOj.js.br +0 -0
  211. package/dist/web/assets/slash-command-picker-DmDs8nOj.js.gz +0 -0
  212. package/dist/web/assets/{sql-object-tab-b1Z2zVBm.js → sql-object-tab-D6P4hzkb.js} +1 -1
  213. package/dist/web/assets/sql-object-tab-D6P4hzkb.js.br +0 -0
  214. package/dist/web/assets/sql-object-tab-D6P4hzkb.js.gz +0 -0
  215. package/dist/web/assets/{sqlite-viewer-DhPv28tq.js → sqlite-viewer-mUitnzHB.js} +1 -1
  216. package/dist/web/assets/sqlite-viewer-mUitnzHB.js.br +0 -0
  217. package/dist/web/assets/sqlite-viewer-mUitnzHB.js.gz +0 -0
  218. package/dist/web/assets/{structure-save-dialog-sGUpqs8D.js → structure-save-dialog-CmbNlmBY.js} +1 -1
  219. package/dist/web/assets/structure-save-dialog-CmbNlmBY.js.br +0 -0
  220. package/dist/web/assets/structure-save-dialog-CmbNlmBY.js.gz +0 -0
  221. package/dist/web/assets/{structure-tab-Bo2OmDOc.js → structure-tab-DCPNuFTe.js} +1 -1
  222. package/dist/web/assets/structure-tab-DCPNuFTe.js.br +0 -0
  223. package/dist/web/assets/structure-tab-DCPNuFTe.js.gz +0 -0
  224. package/dist/web/assets/{system-monitor-body-C475Et9u.js → system-monitor-body-BhxyqSqu.js} +2 -2
  225. package/dist/web/assets/system-monitor-body-BhxyqSqu.js.br +0 -0
  226. package/dist/web/assets/system-monitor-body-BhxyqSqu.js.gz +0 -0
  227. package/dist/web/assets/{system-monitor-tab-Cm52yoW8.js → system-monitor-tab-B_miogQI.js} +1 -1
  228. package/dist/web/assets/{system-monitor-window-content-YYsGHxZF.js → system-monitor-window-content-8IogR6tR.js} +1 -1
  229. package/dist/web/assets/{tab-host-window-content-BKI6QoDa.js → tab-host-window-content-BWHQjgWZ.js} +1 -1
  230. package/dist/web/assets/tab-host-window-content-BWHQjgWZ.js.br +0 -0
  231. package/dist/web/assets/tab-host-window-content-BWHQjgWZ.js.gz +0 -0
  232. package/dist/web/assets/{table-tab-D-nKWMJR.js → table-tab-E3YMueu_.js} +1 -1
  233. package/dist/web/assets/table-tab-E3YMueu_.js.br +0 -0
  234. package/dist/web/assets/{table-tab-D-nKWMJR.js.gz → table-tab-E3YMueu_.js.gz} +0 -0
  235. package/dist/web/assets/{telegram-bot-token-field-BeNS2X2k.js → telegram-bot-token-field-CwW24m62.js} +1 -1
  236. package/dist/web/assets/telegram-bot-token-field-CwW24m62.js.br +0 -0
  237. package/dist/web/assets/telegram-bot-token-field-CwW24m62.js.gz +0 -0
  238. package/dist/web/assets/{terminal-tab-BbP753Ab.js → terminal-tab-BAe5cWEL.js} +1 -1
  239. package/dist/web/assets/terminal-tab-BAe5cWEL.js.br +0 -0
  240. package/dist/web/assets/terminal-tab-BAe5cWEL.js.gz +0 -0
  241. package/dist/web/assets/{tool-cards-Dh8Ekn2W.js → tool-cards-DrCiSH2r.js} +2 -2
  242. package/dist/web/assets/tool-cards-DrCiSH2r.js.br +0 -0
  243. package/dist/web/assets/tool-cards-DrCiSH2r.js.gz +0 -0
  244. package/dist/web/assets/{tools-settings-section-DGZRIULE.js → tools-settings-section-S-vBr2af.js} +1 -1
  245. package/dist/web/assets/tools-settings-section-S-vBr2af.js.br +0 -0
  246. package/dist/web/assets/tools-settings-section-S-vBr2af.js.gz +0 -0
  247. package/dist/web/assets/{use-inline-blame-BxwM-Wkj.js → use-inline-blame-BzVaaCCt.js} +1 -1
  248. package/dist/web/assets/use-inline-blame-BzVaaCCt.js.br +0 -0
  249. package/dist/web/assets/use-inline-blame-BzVaaCCt.js.gz +0 -0
  250. package/dist/web/assets/{use-lsp-BqvwRKmM.js → use-lsp-BwDL9Fqk.js} +1 -1
  251. package/dist/web/assets/use-lsp-BwDL9Fqk.js.br +0 -0
  252. package/dist/web/assets/use-lsp-BwDL9Fqk.js.gz +0 -0
  253. package/dist/web/assets/{use-monaco-theme-DU0EpDHH.js → use-monaco-theme-DljSc3Xo.js} +1 -1
  254. package/dist/web/assets/{use-process-kill-CVN_c4KF.js → use-process-kill-YDcDqDtG.js} +1 -1
  255. package/dist/web/assets/use-process-kill-YDcDqDtG.js.br +0 -0
  256. package/dist/web/assets/use-process-kill-YDcDqDtG.js.gz +0 -0
  257. package/dist/web/assets/{use-sql-schema-info-C0wBmR6-.js → use-sql-schema-info-cAI0vn7d.js} +1 -1
  258. package/dist/web/assets/use-sql-schema-info-cAI0vn7d.js.br +0 -0
  259. package/dist/web/assets/use-sql-schema-info-cAI0vn7d.js.gz +0 -0
  260. package/dist/web/assets/{use-websocket-K0OUXbbY.js → use-websocket-Djo_ruX_.js} +1 -1
  261. package/dist/web/assets/{web-preview-tab-DOgLv8Gt.js → web-preview-tab-DXy0hRef.js} +1 -1
  262. package/dist/web/assets/web-preview-tab-DXy0hRef.js.br +0 -0
  263. package/dist/web/assets/web-preview-tab-DXy0hRef.js.gz +0 -0
  264. package/dist/web/index.html +2 -2
  265. package/dist/web/index.html.br +0 -0
  266. package/dist/web/index.html.gz +0 -0
  267. package/dist/web/sw.js +1 -1
  268. package/dist/web/sw.js.br +0 -0
  269. package/dist/web/sw.js.gz +0 -0
  270. package/docs/architecture/ai-chat-and-providers.md +29 -0
  271. package/docs/lessons-learned.md +14 -4
  272. package/package.json +1 -1
  273. package/src/providers/claude-agent-sdk-query-options.ts +13 -2
  274. package/src/providers/claude-agent-sdk.ts +112 -30
  275. package/src/providers/claude-approval-queue.ts +75 -0
  276. package/src/providers/claude-effort-status.ts +211 -0
  277. package/src/server/routes/chat.ts +17 -0
  278. package/src/server/routes/settings.ts +16 -0
  279. package/src/server/ws/chat-effort-status.ts +73 -0
  280. package/src/server/ws/chat.ts +30 -3
  281. package/src/shared/effort-status.ts +128 -0
  282. package/src/types/api.ts +3 -1
  283. package/src/types/chat.ts +8 -0
  284. package/src/web/components/chat/chat-scroll-nav.tsx +12 -8
  285. package/src/web/components/chat/chat-tab.tsx +15 -11
  286. package/src/web/components/chat/message-action-bar.tsx +19 -4
  287. package/src/web/components/chat/message-input.tsx +6 -0
  288. package/src/web/components/chat/message-list.tsx +55 -21
  289. package/src/web/components/chat/message-user-bubble.tsx +2 -2
  290. package/src/web/components/chat/model-thinking-selector-helpers.ts +79 -0
  291. package/src/web/components/chat/model-thinking-selector.tsx +36 -10
  292. package/src/web/components/chat/session-changes-context.tsx +29 -1
  293. package/src/web/components/layout/reparenting-tab.tsx +53 -14
  294. package/src/web/components/settings/ai-settings-section.tsx +32 -0
  295. package/src/web/hooks/use-chat.ts +16 -0
  296. package/src/web/hooks/use-effort-status.ts +46 -0
  297. package/src/web/hooks/use-relative-time-tick.ts +35 -0
  298. package/src/web/hooks/use-session-file-changes.ts +5 -1
  299. package/src/web/hooks/use-tab-overflow.ts +26 -13
  300. package/src/web/lib/format-date.ts +6 -1
  301. package/dist/web/assets/accounts-settings-section-CiNmIj02.js.br +0 -0
  302. package/dist/web/assets/accounts-settings-section-CiNmIj02.js.gz +0 -0
  303. package/dist/web/assets/agent-session-sheet-DRR36PDA.js.br +0 -0
  304. package/dist/web/assets/agent-session-sheet-DRR36PDA.js.gz +0 -0
  305. package/dist/web/assets/agent-session-window-content-DMhy16BM.js.br +0 -0
  306. package/dist/web/assets/agent-session-window-content-DMhy16BM.js.gz +0 -0
  307. package/dist/web/assets/ai-resource-editor-BG8boPw7.js.br +0 -0
  308. package/dist/web/assets/ai-resource-editor-BG8boPw7.js.gz +0 -0
  309. package/dist/web/assets/ai-settings-section-DMGM1hYx.js +0 -1
  310. package/dist/web/assets/ai-settings-section-DMGM1hYx.js.br +0 -0
  311. package/dist/web/assets/ai-settings-section-DMGM1hYx.js.gz +0 -0
  312. package/dist/web/assets/android-tab-lHDxD93G.js.br +0 -0
  313. package/dist/web/assets/android-tab-lHDxD93G.js.gz +0 -0
  314. package/dist/web/assets/appearance-settings-section-DO-ZyoMj.js.br +0 -0
  315. package/dist/web/assets/appearance-settings-section-DO-ZyoMj.js.gz +0 -0
  316. package/dist/web/assets/apps-panel-CH-0CHYY.js.br +0 -0
  317. package/dist/web/assets/apps-panel-CH-0CHYY.js.gz +0 -0
  318. package/dist/web/assets/branch-review-tab-CdGtvekz.js.br +0 -0
  319. package/dist/web/assets/branch-review-tab-CdGtvekz.js.gz +0 -0
  320. package/dist/web/assets/chat-tab-DMqWnFIo.js.br +0 -0
  321. package/dist/web/assets/chat-tab-DMqWnFIo.js.gz +0 -0
  322. package/dist/web/assets/chat-tab-DzWpwPFb.js +0 -19
  323. package/dist/web/assets/chat-tab-DzWpwPFb.js.br +0 -0
  324. package/dist/web/assets/chat-tab-DzWpwPFb.js.gz +0 -0
  325. package/dist/web/assets/code-editor-BCv8ONNS.js.br +0 -0
  326. package/dist/web/assets/code-editor-BCv8ONNS.js.gz +0 -0
  327. package/dist/web/assets/codex-reset-credit-button-CGMbIHak.js.br +0 -0
  328. package/dist/web/assets/conflict-editor-3WmzqBlW.js.br +0 -0
  329. package/dist/web/assets/conflict-editor-3WmzqBlW.js.gz +0 -0
  330. package/dist/web/assets/connection-form-tab-BoWucvAe.js.br +0 -0
  331. package/dist/web/assets/connection-form-tab-BoWucvAe.js.gz +0 -0
  332. package/dist/web/assets/csv-preview-BCgGsy7F.js.br +0 -0
  333. package/dist/web/assets/csv-preview-BCgGsy7F.js.gz +0 -0
  334. package/dist/web/assets/db-tab-parts-o6QMHBbA.js.br +0 -0
  335. package/dist/web/assets/db-tab-parts-o6QMHBbA.js.gz +0 -0
  336. package/dist/web/assets/design-settings-section-seo6SdSk.js.br +0 -0
  337. package/dist/web/assets/design-settings-section-seo6SdSk.js.gz +0 -0
  338. package/dist/web/assets/design-tab-B-f_b2LM.js.br +0 -0
  339. package/dist/web/assets/design-tab-B-f_b2LM.js.gz +0 -0
  340. package/dist/web/assets/designs-sidebar-panel-CVT_TQP7.js.br +0 -0
  341. package/dist/web/assets/designs-sidebar-panel-CVT_TQP7.js.gz +0 -0
  342. package/dist/web/assets/diff-viewer-y9uBp_8u.js.br +0 -0
  343. package/dist/web/assets/diff-viewer-y9uBp_8u.js.gz +0 -0
  344. package/dist/web/assets/docx-preview-Dn_x3Inm.js.br +0 -0
  345. package/dist/web/assets/docx-preview-Dn_x3Inm.js.gz +0 -0
  346. package/dist/web/assets/editor-dialog-BwNXSajD.js.br +0 -0
  347. package/dist/web/assets/editor-dialog-BwNXSajD.js.gz +0 -0
  348. package/dist/web/assets/editor-language-service-XA_TnPLN.js.br +0 -0
  349. package/dist/web/assets/editor-language-service-XA_TnPLN.js.gz +0 -0
  350. package/dist/web/assets/explorer-body-DukR7zM9.js.br +0 -0
  351. package/dist/web/assets/explorer-body-DukR7zM9.js.gz +0 -0
  352. package/dist/web/assets/explorer-window-content-BjTZ44S3.js.br +0 -0
  353. package/dist/web/assets/explorer-window-content-BjTZ44S3.js.gz +0 -0
  354. package/dist/web/assets/extension-manager-section-C4WD1BPQ.js.br +0 -0
  355. package/dist/web/assets/extension-manager-section-C4WD1BPQ.js.gz +0 -0
  356. package/dist/web/assets/extension-webview-CV2ujflc.js.br +0 -0
  357. package/dist/web/assets/extension-webview-CV2ujflc.js.gz +0 -0
  358. package/dist/web/assets/files-settings-section-hS5ixQ8o.js.br +0 -0
  359. package/dist/web/assets/files-settings-section-hS5ixQ8o.js.gz +0 -0
  360. package/dist/web/assets/format-date-CSHaU2wN.js +0 -1
  361. package/dist/web/assets/general-settings-section-BnKYY0AA.js.br +0 -0
  362. package/dist/web/assets/general-settings-section-BnKYY0AA.js.gz +0 -0
  363. package/dist/web/assets/git-log-panel-JiWfR5xv.js.br +0 -0
  364. package/dist/web/assets/git-log-panel-JiWfR5xv.js.gz +0 -0
  365. package/dist/web/assets/git-review-tab-DgvcY__Q.js.br +0 -0
  366. package/dist/web/assets/git-review-tab-DgvcY__Q.js.gz +0 -0
  367. package/dist/web/assets/glide-data-grid-r1MKxDjz.js.br +0 -0
  368. package/dist/web/assets/glide-data-grid-r1MKxDjz.js.gz +0 -0
  369. package/dist/web/assets/grid-save-dialog-Fa0yA-Ep.js.br +0 -0
  370. package/dist/web/assets/grid-save-dialog-Fa0yA-Ep.js.gz +0 -0
  371. package/dist/web/assets/group-chat-tab-B2Q8yqSX.js.br +0 -0
  372. package/dist/web/assets/group-chat-tab-B2Q8yqSX.js.gz +0 -0
  373. package/dist/web/assets/impexp-tab-B8Ht4l32.js.br +0 -0
  374. package/dist/web/assets/impexp-tab-B8Ht4l32.js.gz +0 -0
  375. package/dist/web/assets/index-BtAn_krv.js.br +0 -0
  376. package/dist/web/assets/index-BtAn_krv.js.gz +0 -0
  377. package/dist/web/assets/keyboard-shortcuts-section-aoiLpWwf.js.br +0 -0
  378. package/dist/web/assets/keyboard-shortcuts-section-aoiLpWwf.js.gz +0 -0
  379. package/dist/web/assets/language-servers-section-BkGAezo6.js.br +0 -0
  380. package/dist/web/assets/language-servers-section-BkGAezo6.js.gz +0 -0
  381. package/dist/web/assets/logs-app-DYgPtmjm.js.br +0 -0
  382. package/dist/web/assets/logs-app-DYgPtmjm.js.gz +0 -0
  383. package/dist/web/assets/lsp-client-CIcphNmU.js.br +0 -0
  384. package/dist/web/assets/lsp-client-CIcphNmU.js.gz +0 -0
  385. package/dist/web/assets/markdown-renderer-dk1w3goH.js.br +0 -0
  386. package/dist/web/assets/markdown-renderer-dk1w3goH.js.gz +0 -0
  387. package/dist/web/assets/mobile-explorer-sheet-D4lPZQZc.js.br +0 -0
  388. package/dist/web/assets/mobile-explorer-sheet-D4lPZQZc.js.gz +0 -0
  389. package/dist/web/assets/new-design-dialog-CIq7xspW.js.br +0 -0
  390. package/dist/web/assets/new-design-dialog-CIq7xspW.js.gz +0 -0
  391. package/dist/web/assets/notifications-settings-section-CoWMtXCC.js.br +0 -0
  392. package/dist/web/assets/notifications-settings-section-CoWMtXCC.js.gz +0 -0
  393. package/dist/web/assets/ppmbot-settings-section-Ct0UwA6T.js.br +0 -0
  394. package/dist/web/assets/ppmbot-settings-section-Ct0UwA6T.js.gz +0 -0
  395. package/dist/web/assets/problems-panel-D-HT-0PE.js.br +0 -0
  396. package/dist/web/assets/problems-panel-D-HT-0PE.js.gz +0 -0
  397. package/dist/web/assets/process-table-CXN0s4Pq.js.br +0 -0
  398. package/dist/web/assets/process-table-CXN0s4Pq.js.gz +0 -0
  399. package/dist/web/assets/proxy-settings-section-e6WWzP8n.js.br +0 -0
  400. package/dist/web/assets/proxy-settings-section-e6WWzP8n.js.gz +0 -0
  401. package/dist/web/assets/query-tab-C7C67t8F.js.br +0 -0
  402. package/dist/web/assets/query-tab-C7C67t8F.js.gz +0 -0
  403. package/dist/web/assets/remote-access-settings-section-gGoATCF9.js.br +0 -0
  404. package/dist/web/assets/remote-access-settings-section-gGoATCF9.js.gz +0 -0
  405. package/dist/web/assets/remote-desktop-clipboard-notice-D5KjvMZx.js.br +0 -0
  406. package/dist/web/assets/remote-desktop-clipboard-notice-D5KjvMZx.js.gz +0 -0
  407. package/dist/web/assets/remote-desktop-mobile-sheet-CY_DsGXZ.js.br +0 -0
  408. package/dist/web/assets/remote-desktop-mobile-sheet-CY_DsGXZ.js.gz +0 -0
  409. package/dist/web/assets/remote-desktop-mobile-view-DdpI-RF8.js.br +0 -0
  410. package/dist/web/assets/remote-desktop-readiness-gate-WIyCCAv5.js.br +0 -0
  411. package/dist/web/assets/remote-desktop-readiness-gate-WIyCCAv5.js.gz +0 -0
  412. package/dist/web/assets/remote-desktop-window-content-BMORZLZt.js.br +0 -0
  413. package/dist/web/assets/remote-desktop-window-content-BMORZLZt.js.gz +0 -0
  414. package/dist/web/assets/rename-object-dialog-pIgWe_AL.js.br +0 -0
  415. package/dist/web/assets/rename-object-dialog-pIgWe_AL.js.gz +0 -0
  416. package/dist/web/assets/review-code-oTNcTYlI.js.br +0 -0
  417. package/dist/web/assets/review-code-oTNcTYlI.js.gz +0 -0
  418. package/dist/web/assets/save-as-dialog-CnPLGTaL.js.br +0 -0
  419. package/dist/web/assets/save-as-dialog-CnPLGTaL.js.gz +0 -0
  420. package/dist/web/assets/schedules-settings-section-CREqHG_i.js.br +0 -0
  421. package/dist/web/assets/schedules-settings-section-CREqHG_i.js.gz +0 -0
  422. package/dist/web/assets/session-review-tab-nFVJYYJH.js.br +0 -0
  423. package/dist/web/assets/session-review-tab-nFVJYYJH.js.gz +0 -0
  424. package/dist/web/assets/settings-body-DNzH0wOQ.js.br +0 -0
  425. package/dist/web/assets/settings-body-DNzH0wOQ.js.gz +0 -0
  426. package/dist/web/assets/settings-rows-D02_4sDY.js.br +0 -0
  427. package/dist/web/assets/settings-rows-D02_4sDY.js.gz +0 -0
  428. package/dist/web/assets/settings-section-content-BYW_9mHv.js +0 -1
  429. package/dist/web/assets/settings-section-content-DWopMfmq.js.br +0 -0
  430. package/dist/web/assets/settings-section-content-DWopMfmq.js.gz +0 -0
  431. package/dist/web/assets/slash-command-picker-Bcizxvbh.js.br +0 -0
  432. package/dist/web/assets/slash-command-picker-Bcizxvbh.js.gz +0 -0
  433. package/dist/web/assets/sql-object-tab-b1Z2zVBm.js.br +0 -0
  434. package/dist/web/assets/sql-object-tab-b1Z2zVBm.js.gz +0 -0
  435. package/dist/web/assets/sqlite-viewer-DhPv28tq.js.br +0 -0
  436. package/dist/web/assets/sqlite-viewer-DhPv28tq.js.gz +0 -0
  437. package/dist/web/assets/structure-save-dialog-sGUpqs8D.js.br +0 -0
  438. package/dist/web/assets/structure-save-dialog-sGUpqs8D.js.gz +0 -0
  439. package/dist/web/assets/structure-tab-Bo2OmDOc.js.br +0 -0
  440. package/dist/web/assets/structure-tab-Bo2OmDOc.js.gz +0 -0
  441. package/dist/web/assets/system-monitor-body-C475Et9u.js.br +0 -0
  442. package/dist/web/assets/system-monitor-body-C475Et9u.js.gz +0 -0
  443. package/dist/web/assets/tab-host-window-content-BKI6QoDa.js.br +0 -0
  444. package/dist/web/assets/tab-host-window-content-BKI6QoDa.js.gz +0 -0
  445. package/dist/web/assets/table-tab-D-nKWMJR.js.br +0 -0
  446. package/dist/web/assets/telegram-bot-token-field-BeNS2X2k.js.br +0 -0
  447. package/dist/web/assets/telegram-bot-token-field-BeNS2X2k.js.gz +0 -0
  448. package/dist/web/assets/terminal-tab-BbP753Ab.js.br +0 -0
  449. package/dist/web/assets/terminal-tab-BbP753Ab.js.gz +0 -0
  450. package/dist/web/assets/tool-cards-Dh8Ekn2W.js.br +0 -0
  451. package/dist/web/assets/tool-cards-Dh8Ekn2W.js.gz +0 -0
  452. package/dist/web/assets/tools-settings-section-DGZRIULE.js.br +0 -0
  453. package/dist/web/assets/tools-settings-section-DGZRIULE.js.gz +0 -0
  454. package/dist/web/assets/use-inline-blame-BxwM-Wkj.js.br +0 -0
  455. package/dist/web/assets/use-inline-blame-BxwM-Wkj.js.gz +0 -0
  456. package/dist/web/assets/use-lsp-BqvwRKmM.js.br +0 -0
  457. package/dist/web/assets/use-lsp-BqvwRKmM.js.gz +0 -0
  458. package/dist/web/assets/use-process-kill-CVN_c4KF.js.br +0 -0
  459. package/dist/web/assets/use-process-kill-CVN_c4KF.js.gz +0 -0
  460. package/dist/web/assets/use-sql-schema-info-C0wBmR6-.js.br +0 -0
  461. package/dist/web/assets/use-sql-schema-info-C0wBmR6-.js.gz +0 -0
  462. package/dist/web/assets/web-preview-tab-DOgLv8Gt.js.br +0 -0
  463. package/dist/web/assets/web-preview-tab-DOgLv8Gt.js.gz +0 -0
@@ -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.
@@ -221,6 +227,14 @@ interface StreamingSession {
221
227
  lastUserContent: string;
222
228
  /** Latest user message images — updated on follow-ups for accurate retry */
223
229
  lastUserImages?: Array<{ data: string; mediaType: string }>;
230
+ /**
231
+ * A user message has been pushed whose turn has not finished.
232
+ *
233
+ * A crash retry may only re-send a prompt while this is true. Without it the retry re-sent
234
+ * whatever content it could find into a session that was simply idle, and an instruction the
235
+ * user gave hours earlier ran a second time as a real turn.
236
+ */
237
+ awaitingReply?: boolean;
224
238
  /** The sign-in this subprocess was started with — see `SubprocessCredential`. */
225
239
  credential: SubprocessCredential;
226
240
  }
@@ -283,6 +297,8 @@ export class ClaudeAgentSdkProvider implements AIProvider {
283
297
  * and this is the only place that knows what made it do so.
284
298
  */
285
299
  private teardownReasons = new Map<string, string>();
300
+ /** The `--effort` each session's CLI was started with, to set beside what it reports sending. */
301
+ private startedEffort = new Map<string, string | null>();
286
302
  /** The API's last rate-limit status per account and limit, so only a change is logged. */
287
303
  private rateLimitStatus = new Map<string, string>();
288
304
  /** CLIs started for chats that do not exist yet — see `prewarm`. */
@@ -738,6 +754,7 @@ export class ClaudeAgentSdkProvider implements AIProvider {
738
754
  }
739
755
  }
740
756
  this.forkSources.delete(sessionId);
757
+ this.startedEffort.delete(sessionId);
741
758
 
742
759
  // Best-effort: delete JSONL from ~/.claude/projects/
743
760
  try {
@@ -829,6 +846,7 @@ export class ClaudeAgentSdkProvider implements AIProvider {
829
846
  // Track latest message for retry paths (fixes stale firstMsg bug)
830
847
  ss.lastUserContent = modelInput;
831
848
  ss.lastUserImages = opts?.images;
849
+ ss.awaitingReply = true;
832
850
  log.debug(`pushMessage: session=${sessionId} priority=${opts?.priority ?? 'next'}`);
833
851
  }
834
852
 
@@ -933,7 +951,7 @@ export class ClaudeAgentSdkProvider implements AIProvider {
933
951
  ...(p.forkSession && { forkSession: true }),
934
952
  cwd: p.cwd,
935
953
  systemPrompt: p.systemPrompt,
936
- settingSources: ["user", "project"],
954
+ settingSources: [...CLAUDE_SETTING_SOURCES],
937
955
  env: p.env,
938
956
  settings: { permissions: { allow: [], deny: [] } },
939
957
  allowedTools: p.allowedTools,
@@ -1177,12 +1195,8 @@ export class ClaudeAgentSdkProvider implements AIProvider {
1177
1195
  const dbToolsMcp = opts?.designSession ? undefined : opts?.dbToolsMcp;
1178
1196
  const allowedTools = allowedToolsFor({ isBypass, agentTeams: providerConfig.agent_teams, designPolicy, designCheckTool });
1179
1197
 
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;
1198
+ /** Approval requests from canUseTool and the PreToolUse hook, handed out by the main loop. */
1199
+ const approvals = new ApprovalQueue<ChatEvent>();
1186
1200
 
1187
1201
  /**
1188
1202
  * Helper: send approval request to FE and wait for response.
@@ -1193,8 +1207,7 @@ export class ClaudeAgentSdkProvider implements AIProvider {
1193
1207
  const promise = new Promise<{ approved: boolean; data?: unknown }>((resolve) => {
1194
1208
  this.pendingApprovals.set(requestId, { resolve, sessionId });
1195
1209
  });
1196
- approvalEvents.push({ type: "approval_request", requestId, tool: toolName, input });
1197
- approvalNotify?.();
1210
+ approvals.push({ type: "approval_request", requestId, tool: toolName, input });
1198
1211
  return promise;
1199
1212
  };
1200
1213
 
@@ -1460,12 +1473,32 @@ export class ClaudeAgentSdkProvider implements AIProvider {
1460
1473
  providerConfig,
1461
1474
  stderr: stderrCallback,
1462
1475
  });
1476
+ // Every retry below restarts the CLI with these same options.
1477
+ this.startedEffort.set(sessionId, (queryOptions.effort as string | undefined) ?? null);
1463
1478
 
1464
1479
  // Crash retry: if subprocess exits with non-zero code before producing events,
1465
1480
  // clean up and retry once with a fresh query before surfacing the error.
1466
1481
  const MAX_CRASH_RETRIES = 1;
1467
1482
  let crashRetryCount = 0;
1468
1483
 
1484
+ /**
1485
+ * What the crashed subprocess was doing, read off its session entry before that entry is
1486
+ * deleted. Undefined until a crash. `awaiting` false means no turn was in flight, and a
1487
+ * retry must then push nothing at all: the session is only being rebuilt so the next real
1488
+ * message has somewhere to go.
1489
+ */
1490
+ let crashed: { content: string; images?: Array<{ data: string; mediaType: string }>; awaiting: boolean } | undefined;
1491
+
1492
+ // Once the turn has produced output (top-level assistant text/tool_use), the user
1493
+ // message + any tool_results are persisted to JSONL. Mid-turn retries must resume and
1494
+ // continue — NOT re-push the original message, which restarts the turn and re-displays
1495
+ // an already-answered AskUserQuestion.
1496
+ //
1497
+ // Declared outside `crashRetryLoop` on purpose: inside it, a crash re-entered the loop
1498
+ // body and cleared the very flag that keeps the retry from re-sending the prompt. It is
1499
+ // reset at the end of each turn instead.
1500
+ let turnProgressed = false;
1501
+
1469
1502
  crashRetryLoop: for (;;) {
1470
1503
  try {
1471
1504
  // Streaming input: create message channel and persistent query.
@@ -1479,11 +1512,6 @@ export class ClaudeAgentSdkProvider implements AIProvider {
1479
1512
  session_id: sessionId,
1480
1513
  };
1481
1514
 
1482
- // Once the turn has produced output (top-level assistant text/tool_use), the user
1483
- // message + any tool_results are persisted to JSONL. Mid-turn retries must resume and
1484
- // continue — NOT re-push the original message, which restarts the turn and re-displays
1485
- // an already-answered AskUserQuestion. Persists across retryLoop iterations.
1486
- let turnProgressed = false;
1487
1515
 
1488
1516
  // Build a retry message for token-refresh / transient-error retries.
1489
1517
  // Pre-turn (nothing persisted yet): re-push the LATEST user content so the SDK has a
@@ -1494,8 +1522,10 @@ export class ClaudeAgentSdkProvider implements AIProvider {
1494
1522
  // Also returns the raw content/images for re-populating the new streaming session.
1495
1523
  const buildRetryMsg = () => {
1496
1524
  const ss = this.streamingSessions.get(sessionId);
1497
- const content = ss?.lastUserContent ?? modelInput;
1498
- const images = ss?.lastUserImages;
1525
+ // `crashed` is the only source once the entry is gone: `?? modelInput` would otherwise
1526
+ // reach past every follow-up to the message that opened the stream, hours earlier.
1527
+ const content = ss?.lastUserContent ?? crashed?.content ?? modelInput;
1528
+ const images = ss?.lastUserImages ?? crashed?.images;
1499
1529
  const retryContent = turnProgressed ? "Continue from where you left off." : content;
1500
1530
  return {
1501
1531
  msg: {
@@ -1516,9 +1546,14 @@ export class ClaudeAgentSdkProvider implements AIProvider {
1516
1546
  : undefined;
1517
1547
  const channel = spare ? undefined : createMessageChannel();
1518
1548
  const initialCtrl = spare?.controller ?? channel!.controller;
1519
- // On crash retry, use buildRetryMsg to get the latest user message (not the stale firstMsg)
1549
+ // On crash retry, use buildRetryMsg to get the latest user message (not the stale firstMsg).
1550
+ // A crash with no turn in flight pushes NOTHING: the subprocess is replaced so the next real
1551
+ // message has somewhere to go, and re-sending a finished instruction runs it a second time.
1520
1552
  const initRetry = crashRetryCount > 0 ? buildRetryMsg() : null;
1521
- initialCtrl.push(initRetry?.msg ?? firstMsg);
1553
+ const resendOnCrash = crashRetryCount > 0 && crashed?.awaiting === true;
1554
+ if (crashRetryCount === 0) initialCtrl.push(firstMsg);
1555
+ else if (resendOnCrash && initRetry) initialCtrl.push(initRetry.msg);
1556
+ else log.info(`session=${sessionId} crash retry rebuilt the subprocess with no turn to resume — nothing re-sent`);
1522
1557
  const initContent = initRetry?.lastUserContent ?? modelInput;
1523
1558
  const initImages = initRetry?.lastUserImages ?? opts?.images;
1524
1559
 
@@ -1533,7 +1568,7 @@ export class ClaudeAgentSdkProvider implements AIProvider {
1533
1568
  // The query's one start line: a warm CLI and the MCP servers belong to it.
1534
1569
  log.info(`query: session=${sessionId} isFirst=${isFirstMessage} fork=${shouldFork} cwd=${effectiveCwd} platform=${process.platform} accountMode=${!!account} permissionMode=${permissionMode} isBypass=${isBypass}`
1535
1570
  + ` cli=${spare ? `warm ageMs=${spare.ageMs}` : "cold"}${crashRetryCount > 0 ? ` crashRetry=${crashRetryCount}` : ""} mcp=${Object.keys(mcpServers).join(",") || "none"}`);
1536
- this.streamingSessions.set(sessionId, { meta, query: initialQuery, controller: initialCtrl, lastUserContent: initContent, lastUserImages: initImages, credential: queryCredential });
1571
+ this.streamingSessions.set(sessionId, { meta, query: initialQuery, controller: initialCtrl, lastUserContent: initContent, lastUserImages: initImages, awaitingReply: crashRetryCount === 0 || resendOnCrash, credential: queryCredential });
1537
1572
  this.activeQueries.set(sessionId, initialQuery);
1538
1573
  let eventSource: AsyncIterable<any> = initialQuery;
1539
1574
  log.debug(`session=${sessionId} query() created, waiting for first SDK event...`);
@@ -1610,7 +1645,12 @@ export class ClaudeAgentSdkProvider implements AIProvider {
1610
1645
  assistantContent = "";
1611
1646
  pendingToolCount = 0;
1612
1647
  let sdkEventCount = 0;
1613
- for await (const msg of eventSource) {
1648
+ for await (const msg of approvals.alongside(eventSource)) {
1649
+ // An approval goes out now: the CLI sends nothing more until it is answered.
1650
+ if (msg === APPROVAL_WAKE) {
1651
+ yield* approvals.drain();
1652
+ continue;
1653
+ }
1614
1654
  sdkEventCount++;
1615
1655
  hadAnyEvents = true;
1616
1656
  if (sdkEventCount === 1) {
@@ -1640,9 +1680,7 @@ export class ClaudeAgentSdkProvider implements AIProvider {
1640
1680
  const parentId = (msg as any).parent_tool_use_id as string | undefined;
1641
1681
 
1642
1682
  // Yield any queued approval events
1643
- while (approvalEvents.length > 0) {
1644
- yield approvalEvents.shift()!;
1645
- }
1683
+ yield* approvals.drain();
1646
1684
 
1647
1685
  // Log system events for debugging SDK lifecycle: the subtype only, never the message.
1648
1686
  // thinking_tokens and status=requesting arrive at token rate, so not even at DEBUG.
@@ -2395,6 +2433,12 @@ export class ClaudeAgentSdkProvider implements AIProvider {
2395
2433
  }
2396
2434
  }
2397
2435
 
2436
+ // The turn is over: a crash from here on has nothing to resume, and must not re-send
2437
+ // this instruction. `turnProgressed` goes with it, so the next turn starts clean.
2438
+ const finished = this.streamingSessions.get(sessionId);
2439
+ if (finished) finished.awaitingReply = false;
2440
+ turnProgressed = false;
2441
+
2398
2442
  // Streaming input: yield done for this turn, then continue for next turn
2399
2443
  yieldedDone = true;
2400
2444
  yield {
@@ -2430,9 +2474,7 @@ export class ClaudeAgentSdkProvider implements AIProvider {
2430
2474
  }
2431
2475
 
2432
2476
  // Yield remaining approval events
2433
- while (approvalEvents.length > 0) {
2434
- yield approvalEvents.shift()!;
2435
- }
2477
+ yield* approvals.drain();
2436
2478
 
2437
2479
  if (!hadAnyEvents) {
2438
2480
  yield { type: "error", message: "Claude did not respond. Check that 'claude' CLI works in your terminal." };
@@ -2444,10 +2486,17 @@ export class ClaudeAgentSdkProvider implements AIProvider {
2444
2486
  const crashMsg = (crashErr as Error).message ?? String(crashErr);
2445
2487
  const stderrInfo = stderrBuffer.trim() ? ` stderr: ${stderrBuffer.trim().slice(-500)}` : "";
2446
2488
 
2447
- // Clean up crashed subprocess before retry or error
2489
+ // Clean up crashed subprocess before retry or error. Read what the turn was doing FIRST:
2490
+ // the retry below needs it, and deleting the entry is what used to send it back to the
2491
+ // message that opened the stream.
2448
2492
  this.activeQueries.delete(sessionId);
2449
2493
  const ss = this.streamingSessions.get(sessionId);
2450
- if (ss) { ss.controller.done(); ss.query.close(); this.streamingSessions.delete(sessionId); }
2494
+ if (ss) {
2495
+ crashed = { content: ss.lastUserContent, images: ss.lastUserImages, awaiting: ss.awaitingReply === true };
2496
+ ss.controller.done(); ss.query.close(); this.streamingSessions.delete(sessionId);
2497
+ } else {
2498
+ crashed = { content: modelInput, images: opts?.images, awaiting: false };
2499
+ }
2451
2500
  log.debug(`session=${sessionId} streaming session ended`);
2452
2501
 
2453
2502
  // Each case below logs itself: a cancel or a closed socket is not a failure.
@@ -2533,7 +2582,7 @@ export class ClaudeAgentSdkProvider implements AIProvider {
2533
2582
  ...(needsNodeInterpreter(process.platform, cliExecutablePath) && { executable: "node" }),
2534
2583
  ...(cliExecutablePath && { pathToClaudeCodeExecutable: cliExecutablePath }),
2535
2584
  cwd,
2536
- settingSources: ["user", "project"],
2585
+ settingSources: [...CLAUDE_SETTING_SOURCES],
2537
2586
  // No account: MCP OAuth tokens belong to the CLI's own credential store, the same
2538
2587
  // one an interactive `claude` writes, and no model call is ever made here.
2539
2588
  env: this.buildQueryEnv(cwd, null),
@@ -2578,6 +2627,39 @@ export class ClaudeAgentSdkProvider implements AIProvider {
2578
2627
  }
2579
2628
  }
2580
2629
 
2630
+ /**
2631
+ * The effort this session's CLI sends and what overrides the pick, asked of the CLI itself:
2632
+ * `CLAUDE_CODE_EFFORT_LEVEL`, a settings cap, an organization limit and the model's own levels
2633
+ * all count there, while the `--effort` PPM passed is only one input to it. Null when the
2634
+ * session has no live subprocess or the CLI does not answer.
2635
+ */
2636
+ async readEffortStatus(sessionId: string): Promise<EffortStatus | null> {
2637
+ const ss = this.streamingSessions.get(sessionId);
2638
+ if (!ss) return null;
2639
+ const cli = ss.query as unknown as EffortSettingsQuery;
2640
+ try {
2641
+ const [answer, init] = await Promise.all([
2642
+ withTimeout(cli.getSettings(), EFFORT_READ_TIMEOUT_MS, "Claude did not report its settings"),
2643
+ withTimeout(cli.initializationResult(), EFFORT_READ_TIMEOUT_MS, "Claude did not report its models").catch(() => undefined),
2644
+ ]);
2645
+ return effortStatusFromCli({
2646
+ answer,
2647
+ models: init?.models,
2648
+ requested: this.startedEffort.get(sessionId),
2649
+ processEnv: process.env[EFFORT_ENV_VAR],
2650
+ userFile: userSettingsFile(process.env).shown,
2651
+ });
2652
+ } catch (e) {
2653
+ log.debug(`session=${sessionId} effort status unavailable: ${(e as Error).message}`);
2654
+ return null;
2655
+ }
2656
+ }
2657
+
2658
+ /** What the settings files say about effort for a chat in `projectPath` no CLI has answered for yet. */
2659
+ estimateEffortStatus(projectPath: string | undefined, model?: string): EffortStatus {
2660
+ return estimateEffortStatus({ projectPath, model, env: process.env });
2661
+ }
2662
+
2581
2663
  /** Abort and fully teardown the streaming session — user must resume to continue */
2582
2664
  abortQuery(sessionId: string, source = "unknown"): void {
2583
2665
  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
+ }
@@ -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 {
@@ -314,6 +314,22 @@ settingsRoutes.get("/ai/providers/:id/models", async (c) => {
314
314
  }
315
315
  });
316
316
 
317
+ /**
318
+ * GET /settings/ai/providers/:id/effort-status — what overrides the default effort outside PPM for
319
+ * every project: the user settings and the environment PPM runs in. A project's own settings can
320
+ * add more, which the chat's picker reports. `status` is null for a provider nothing overrides.
321
+ */
322
+ settingsRoutes.get("/ai/providers/:id/effort-status", (c) => {
323
+ const id = c.req.param("id");
324
+ const provider = providerRegistry.get(id);
325
+ if (!provider) return c.json(err(`Provider "${id}" not found`), 404);
326
+ try {
327
+ return c.json(ok({ status: provider.estimateEffortStatus?.(undefined, c.req.query("model") || undefined) ?? null }));
328
+ } catch (e) {
329
+ return c.json(err((e as Error).message), 500);
330
+ }
331
+ });
332
+
317
333
  // ── Keybindings ──────────────────────────────────────────────────────
318
334
 
319
335
  const KEYBINDINGS_KEY = "keybindings";