@hienlh/ppm 0.23.22 → 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 (463) hide show
  1. package/CHANGELOG.md +11 -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 +58 -18
  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.
@@ -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
+ }
@@ -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";
@@ -0,0 +1,73 @@
1
+ /**
2
+ * The effort a chat's turns really run at, kept beside the pick.
3
+ *
4
+ * PPM starts the CLI with the picked `--effort`, but `CLAUDE_CODE_EFFORT_LEVEL` (in the
5
+ * environment or a settings file's `env`), a `maxEffortLevel` cap, an organization limit and the
6
+ * model itself all outrank it there. So the session's CLI is asked at the start of every turn —
7
+ * a settings file edited since the last one shows up there too — and its answer goes to the
8
+ * clients. Before any turn has asked, what the settings files say stands in.
9
+ */
10
+ import { effortInEffect, effortSettingPlace, type EffortStatus } from "../../shared/effort-status.ts";
11
+
12
+ export interface EffortStatusSession {
13
+ providerId: string;
14
+ projectPath?: string;
15
+ /** The CLI's last answer for this session. */
16
+ effortStatus?: EffortStatus;
17
+ }
18
+
19
+ export interface EffortStatusProvider {
20
+ readEffortStatus?(sessionId: string): Promise<EffortStatus | null>;
21
+ estimateEffortStatus?(projectPath: string | undefined, model?: string): EffortStatus;
22
+ }
23
+
24
+ /** What a greeting carries: the CLI's answer, else the settings files; null for a provider nothing overrides. */
25
+ export function sessionEffortStatus(
26
+ provider: EffortStatusProvider | undefined,
27
+ session: EffortStatusSession,
28
+ model: string | undefined,
29
+ ): EffortStatus | null {
30
+ return session.effortStatus ?? provider?.estimateEffortStatus?.(session.projectPath, model) ?? null;
31
+ }
32
+
33
+ /** The session log's line for a turn whose effort is not the one it was started with. */
34
+ export function effortOverrideLogLine(status: EffortStatus): string | null {
35
+ const { observed } = status;
36
+ if (!observed?.requested) return null;
37
+ const { level, reason } = effortInEffect(observed.requested, status);
38
+ if (!reason) return null;
39
+ const by = reason === "env" && status.env
40
+ ? `CLAUDE_CODE_EFFORT_LEVEL=${status.env.value} in ${effortSettingPlace(status.env)}`
41
+ : reason === "cap" && status.cap
42
+ ? `maxEffortLevel ${status.cap.value} in ${effortSettingPlace(status.cap)}`
43
+ : `what ${observed.model ?? "the model"} allows`;
44
+ return `Effort ${observed.requested} was picked; Claude Code sends ${level ?? "no effort"} (${by})`;
45
+ }
46
+
47
+ /**
48
+ * Ask the session's CLI at a turn's start, and tell the clients when its answer changed. Never
49
+ * rejects: it runs unawaited beside the turn, where a rejection would reach the server's
50
+ * fatal-error handler for what is only a label.
51
+ */
52
+ export async function refreshEffortStatus(p: {
53
+ sessionId: string;
54
+ session: EffortStatusSession;
55
+ provider: EffortStatusProvider | undefined;
56
+ /** False once the session's entry was replaced while the CLI was answering. */
57
+ isCurrent: () => boolean;
58
+ broadcast: (event: unknown) => void;
59
+ log: (line: string) => void;
60
+ }): Promise<void> {
61
+ try {
62
+ const status = await p.provider?.readEffortStatus?.(p.sessionId);
63
+ if (!status || !p.isCurrent()) return;
64
+ const before = p.session.effortStatus;
65
+ p.session.effortStatus = status;
66
+ if (before && JSON.stringify(before) === JSON.stringify(status)) return;
67
+ p.broadcast({ type: "effort_status", status });
68
+ const line = effortOverrideLogLine(status);
69
+ if (line) p.log(line);
70
+ } catch {
71
+ // The status stays as it was; the next turn asks again.
72
+ }
73
+ }