@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
@@ -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
+ }
@@ -24,6 +24,8 @@ import { isAsyncAgentLaunchAck, isTerminalAgentStatus } from "../../shared/backg
24
24
  import { cacheReleaseDelayMs, selectWarmIdleEvictions } from "../../services/subprocess-retention.ts";
25
25
  import { needsAuthServerNames } from "../../services/mcp-oauth/mcp-oauth-redirect.ts";
26
26
  import { mcpStatusEvent, registerMcpSignInSync } from "./chat-mcp-sign-in-sync.ts";
27
+ import { refreshEffortStatus, sessionEffortStatus } from "./chat-effort-status.ts";
28
+ import type { EffortStatus } from "../../shared/effort-status.ts";
27
29
  import { claudeTranscriptExists } from "../../services/claude-transcript-exists.ts";
28
30
  import { registerMemoryGauge } from "../../services/memory-diagnostics.ts";
29
31
  import { setTabOpenDelivery, tabOpenBroker } from "../../services/tab-tools-mcp/tab-open-broker.ts";
@@ -114,6 +116,11 @@ function resolveSessionEffort(sessionId: string): string | undefined {
114
116
  return getSessionEffort(sessionId) ?? sessionProviderConfig(sessionId)?.effort;
115
117
  }
116
118
 
119
+ /** What overrides the effort shown in session_state, if anything does. */
120
+ function resolveEffortStatus(sessionId: string, entry: SessionEntry): EffortStatus | null {
121
+ return sessionEffortStatus(providerRegistry.get(entry.providerId), entry, resolveSessionModel(sessionId));
122
+ }
123
+
117
124
  /** Whether thinking is effectively ON: per-session override wins, else provider config, else SDK default. */
118
125
  function resolveSessionThinkingEnabled(sessionId: string): boolean {
119
126
  return isThinkingEnabled(
@@ -196,6 +203,8 @@ interface SessionEntry {
196
203
  compactStatus?: "compacting" | null;
197
204
  /** MCP servers the subprocess reported as `needs-auth` at init — drives the chat's sign-in bar */
198
205
  mcpNeedsAuth?: string[];
206
+ /** What the subprocess said at the last turn's start about the effort it sends — see chat-effort-status.ts */
207
+ effortStatus?: EffortStatus;
199
208
  /** toolUseIds of Bash/Agent calls launched with run_in_background — their spy outlives the tool_result */
200
209
  backgroundToolUseIds?: Set<string>;
201
210
  /** Nested-agent children buffered into turnEvents this turn (see MAX_NESTED_TURN_EVENTS) */
@@ -957,9 +966,20 @@ async function startSessionConsumer(sessionId: string, providerId: string, conte
957
966
  // System events → transition connecting → thinking, forward compact events
958
967
  if (evType === "system") {
959
968
  const sub = (ev as any).subtype;
960
- if (sub === "init" && Array.isArray(ev.mcpServers)) {
961
- entry.mcpNeedsAuth = needsAuthServerNames(ev.mcpServers);
962
- broadcast(sessionId, mcpStatusEvent(entry.mcpNeedsAuth));
969
+ if (sub === "init") {
970
+ if (Array.isArray(ev.mcpServers)) {
971
+ entry.mcpNeedsAuth = needsAuthServerNames(ev.mcpServers);
972
+ broadcast(sessionId, mcpStatusEvent(entry.mcpNeedsAuth));
973
+ }
974
+ // Not awaited: the turn goes on while the CLI answers.
975
+ void refreshEffortStatus({
976
+ sessionId,
977
+ session: entry,
978
+ provider: providerRegistry.get(entry.providerId),
979
+ isCurrent: () => activeSessions.get(sessionId) === entry,
980
+ broadcast: (event) => broadcast(sessionId, event),
981
+ log: (line) => logSessionEvent(sessionId, "INFO", line),
982
+ });
963
983
  } else if (sub === "compacting") {
964
984
  entry.compactStatus = "compacting";
965
985
  log.debug(`session=${sessionId} compact_status=compacting (persisted on entry)`);
@@ -1388,6 +1408,7 @@ export const chatWebSocket = {
1388
1408
  mcpNeedsAuth: existing.mcpNeedsAuth ?? [],
1389
1409
  model: resolveSessionModel(sessionId),
1390
1410
  effort: resolveSessionEffort(sessionId),
1411
+ effortStatus: resolveEffortStatus(sessionId, existing),
1391
1412
  thinking: resolveSessionThinkingEnabled(sessionId),
1392
1413
  promptCache: promptCacheSnapshot(sessionId, existing),
1393
1414
  turnStop: existing.phase === "idle" ? lastTurnStop(sessionId) : null,
@@ -1557,6 +1578,7 @@ export const chatWebSocket = {
1557
1578
  mcpNeedsAuth: entry.mcpNeedsAuth ?? [],
1558
1579
  model: resolveSessionModel(sessionId),
1559
1580
  effort: resolveSessionEffort(sessionId),
1581
+ effortStatus: resolveEffortStatus(sessionId, entry),
1560
1582
  thinking: resolveSessionThinkingEnabled(sessionId),
1561
1583
  promptCache: promptCacheSnapshot(sessionId, entry),
1562
1584
  turnStop: entry.phase === "idle" ? lastTurnStop(sessionId) : null,
@@ -1779,6 +1801,8 @@ export const chatWebSocket = {
1779
1801
  }
1780
1802
  entry.model = parsed.model;
1781
1803
  setSessionModel(sessionId, parsed.model);
1804
+ // Its levels and limits were the old model's; the settings files stand in until the next turn asks.
1805
+ entry.effortStatus = undefined;
1782
1806
  const provider = providerRegistry.get(providerId);
1783
1807
  const hasLiveStream = provider?.hasStreamingSession?.(sessionId) ?? false;
1784
1808
  // Only abort when idle between turns — never interrupt an active turn.
@@ -1798,6 +1822,7 @@ export const chatWebSocket = {
1798
1822
  mcpNeedsAuth: entry.mcpNeedsAuth ?? [],
1799
1823
  model: resolveSessionModel(sessionId),
1800
1824
  effort: resolveSessionEffort(sessionId),
1825
+ effortStatus: resolveEffortStatus(sessionId, entry),
1801
1826
  thinking: resolveSessionThinkingEnabled(sessionId),
1802
1827
  }));
1803
1828
  } else if (parsed.type === "set_effort") {
@@ -1825,6 +1850,7 @@ export const chatWebSocket = {
1825
1850
  mcpNeedsAuth: entry.mcpNeedsAuth ?? [],
1826
1851
  model: resolveSessionModel(sessionId),
1827
1852
  effort: resolveSessionEffort(sessionId),
1853
+ effortStatus: resolveEffortStatus(sessionId, entry),
1828
1854
  thinking: resolveSessionThinkingEnabled(sessionId),
1829
1855
  }));
1830
1856
  } else if (parsed.type === "set_thinking") {
@@ -1846,6 +1872,7 @@ export const chatWebSocket = {
1846
1872
  mcpNeedsAuth: entry.mcpNeedsAuth ?? [],
1847
1873
  model: resolveSessionModel(sessionId),
1848
1874
  effort: resolveSessionEffort(sessionId),
1875
+ effortStatus: resolveEffortStatus(sessionId, entry),
1849
1876
  thinking: resolveSessionThinkingEnabled(sessionId),
1850
1877
  }));
1851
1878
  } else if (parsed.type === "cancel") {
@@ -0,0 +1,128 @@
1
+ /**
2
+ * The effort a Claude session actually runs at, which is not always the one picked in PPM.
3
+ *
4
+ * Claude Code settles it in this order (measured against the bundled CLI's `get_settings`):
5
+ * `CLAUDE_CODE_EFFORT_LEVEL` beats the `--effort` PPM passes, whether it is in the environment
6
+ * or in the `env` block of a settings file, and a settings file beats the environment; a
7
+ * `maxEffortLevel` in the settings then clamps what is left; and a model without a level runs
8
+ * Extra and Max as High, or sends no effort at all. With
9
+ * `"env": { "CLAUDE_CODE_EFFORT_LEVEL": "max" }` in `~/.claude/settings.json`, every PPM chat
10
+ * ran at Max while the picker said High.
11
+ */
12
+
13
+ export const EFFORT_LEVELS = ["low", "medium", "high", "xhigh", "max"] as const;
14
+ export type EffortLevel = (typeof EFFORT_LEVELS)[number];
15
+
16
+ export function isEffortLevel(value: unknown): value is EffortLevel {
17
+ return typeof value === "string" && (EFFORT_LEVELS as readonly string[]).includes(value);
18
+ }
19
+
20
+ export function effortRank(level: EffortLevel): number {
21
+ return EFFORT_LEVELS.indexOf(level);
22
+ }
23
+
24
+ /** A settings file, as the CLI names its sources, or the environment PPM starts the CLI with. */
25
+ export type EffortSettingSource =
26
+ | "userSettings"
27
+ | "projectSettings"
28
+ | "localSettings"
29
+ | "flagSettings"
30
+ | "policySettings"
31
+ | "environment";
32
+
33
+ export interface EffortSetting<T> {
34
+ value: T;
35
+ source: EffortSettingSource;
36
+ /** The file it is written in, the way the user would look for it. */
37
+ file?: string;
38
+ }
39
+
40
+ export interface EffortStatus {
41
+ /** `CLAUDE_CODE_EFFORT_LEVEL` as the CLI reads it. Absent when unset or a value the CLI ignores. */
42
+ env?: EffortSetting<string>;
43
+ /** The lowest `maxEffortLevel` the settings files set for the session's model. */
44
+ cap?: EffortSetting<EffortLevel>;
45
+ /**
46
+ * The running CLI's own answer: the effort it was started with and the one it sends. Only
47
+ * the CLI knows its organization's limits and what its model takes, so this outranks the rest.
48
+ */
49
+ observed?: { requested: EffortLevel | null; applied: EffortLevel | null; model?: string };
50
+ /** Levels the model runs as themselves, [] when it takes none. Absent when the CLI did not say. */
51
+ supported?: EffortLevel[];
52
+ }
53
+
54
+ export type EffortEnvMeaning =
55
+ | { kind: "level"; level: EffortLevel }
56
+ /** `auto` or `unset`: no effort is sent and the model's default applies. */
57
+ | { kind: "auto" }
58
+ /** A number: a thinking budget, sent instead of an effort level. */
59
+ | { kind: "budget" };
60
+
61
+ /** What the CLI makes of `CLAUDE_CODE_EFFORT_LEVEL`, parsed as it parses it; null for a value it ignores. */
62
+ export function effortEnvMeaning(raw: string | null | undefined): EffortEnvMeaning | null {
63
+ if (!raw) return null;
64
+ const value = raw.toLowerCase();
65
+ if (value === "auto" || value === "unset") return { kind: "auto" };
66
+ const level = value === "med" ? "medium" : value;
67
+ if (isEffortLevel(level)) return { kind: "level", level };
68
+ return Number.isNaN(parseInt(value, 10)) ? null : { kind: "budget" };
69
+ }
70
+
71
+ export interface EffortInEffect {
72
+ /** The level each request carries; null when none is sent, or when only the CLI can tell which. */
73
+ level: EffortLevel | null;
74
+ /** What decided it when that is not simply the pick. */
75
+ reason: "env" | "cap" | "model" | null;
76
+ }
77
+
78
+ /** The effort a turn runs at given the level picked in PPM, and what overrides the pick if anything does. */
79
+ export function effortInEffect(pick: EffortLevel, status: EffortStatus | null | undefined): EffortInEffect {
80
+ if (!status) return { level: pick, reason: null };
81
+ const env = effortEnvMeaning(status.env?.value);
82
+ const { observed } = status;
83
+ if (env) {
84
+ // The variable outranks every pick, so the CLI's answer stays true whatever is picked since.
85
+ if (observed) return { level: observed.applied, reason: "env" };
86
+ return { level: envLevel(env, status), reason: "env" };
87
+ }
88
+ // A pick made after the CLI last answered is checked against the limits it reported.
89
+ const level = observed && observed.requested === pick ? observed.applied : withinLimits(pick, status);
90
+ if (level === pick) return { level, reason: null };
91
+ const capped = !!status.cap && effortRank(pick) > effortRank(status.cap.value);
92
+ return { level, reason: capped ? "cap" : "model" };
93
+ }
94
+
95
+ /**
96
+ * What the CLI makes of the variable before it has said so itself. A settings cap changes it:
97
+ * a number is then read as High and `auto` sends the model's own default, each clamped to the cap,
98
+ * where without one neither sends an effort. That default differs by model (measured: Medium on
99
+ * Opus 5.5, High on Opus 4.5, 4.6 and Sonnet 4.6), so under any cap but Low, which leaves nothing
100
+ * lower, only the CLI's answer can say which level `auto` gives.
101
+ */
102
+ function envLevel(env: EffortEnvMeaning, status: EffortStatus): EffortLevel | null {
103
+ if (env.kind === "level") return withinLimits(env.level, status);
104
+ if (!status.cap) return null;
105
+ if (env.kind === "budget") return withinLimits("high", status);
106
+ return status.cap.value === "low" ? withinLimits("low", status) : null;
107
+ }
108
+
109
+ function withinLimits(level: EffortLevel, status: EffortStatus): EffortLevel | null {
110
+ if (status.supported?.length === 0) return null;
111
+ const capped = status.cap && effortRank(level) > effortRank(status.cap.value) ? status.cap.value : level;
112
+ // The CLI's own fallback for a model without Extra or Max.
113
+ const unsupported = status.supported && !status.supported.includes(capped);
114
+ return unsupported && (capped === "xhigh" || capped === "max") ? "high" : capped;
115
+ }
116
+
117
+ /** Where a setting lives, for a sentence that tells the user where to change it. */
118
+ export function effortSettingPlace(setting: EffortSetting<unknown>): string {
119
+ if (setting.file) return setting.file;
120
+ switch (setting.source) {
121
+ case "userSettings": return "~/.claude/settings.json";
122
+ case "projectSettings": return ".claude/settings.json";
123
+ case "localSettings": return ".claude/settings.local.json";
124
+ case "policySettings": return "managed settings";
125
+ case "flagSettings": return "the settings PPM passes to Claude Code";
126
+ case "environment": return "the environment PPM runs in";
127
+ }
128
+ }
package/src/types/api.ts CHANGED
@@ -77,7 +77,9 @@ export type ChatWsServerMessage =
77
77
  | { type: "error"; message: string }
78
78
  | { type: "account_info"; accountId: string; accountLabel: string }
79
79
  | { type: "phase_changed"; phase: SessionPhase; elapsed?: number }
80
- | { type: "session_state"; sessionId: string; phase: SessionPhase; pendingApproval: { requestId: string; tool: string; input: unknown } | null; sessionTitle: string | null; model?: string; effort?: string; thinking?: boolean; turnStop?: import("../shared/turn-stop").TurnStop | null }
80
+ | { type: "session_state"; sessionId: string; phase: SessionPhase; pendingApproval: { requestId: string; tool: string; input: unknown } | null; sessionTitle: string | null; model?: string; effort?: string; thinking?: boolean; effortStatus?: import("../shared/effort-status").EffortStatus | null; turnStop?: import("../shared/turn-stop").TurnStop | null }
81
+ /** What the session's CLI said about its effort when the turn started. */
82
+ | { type: "effort_status"; status: import("../shared/effort-status").EffortStatus }
81
83
  /** The turn that just ended was ended by an error. Sent just before its `done`. */
82
84
  | { type: "turn_stop"; stop: import("../shared/turn-stop").TurnStop }
83
85
  | { type: "turn_events"; events: unknown[]; streamSeq?: number; truncated?: boolean }
package/src/types/chat.ts CHANGED
@@ -106,6 +106,14 @@ export interface AIProvider {
106
106
  getUsage?(sessionId?: string, pickedAccountId?: string): Promise<UsageInfo>;
107
107
  /** True when a live streaming subprocess exists for this session */
108
108
  hasStreamingSession?(sessionId: string): boolean;
109
+ /**
110
+ * The effort the session's live subprocess sends and what overrides the pick there; null
111
+ * when none is running or it cannot say. Only providers whose effort can be overridden
112
+ * outside PPM implement it.
113
+ */
114
+ readEffortStatus?(sessionId: string): Promise<import("../shared/effort-status").EffortStatus | null>;
115
+ /** The same overrides, worked out from settings files for a session nothing has started yet. */
116
+ estimateEffortStatus?(projectPath: string | undefined, model?: string): import("../shared/effort-status").EffortStatus;
109
117
  /** Prompt-cache lifetime for this session, in ms — how long holding its subprocess pays. */
110
118
  promptCacheTtlMs?(sessionId: string): number;
111
119
  }
@@ -1,4 +1,4 @@
1
- import { useCallback, useEffect, useRef, useState } from "react";
1
+ import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
2
2
  import { ChevronUp, ChevronDown, ChevronsDown, ChevronsUpDown } from "@/lib/icons";
3
3
  import { cn } from "@/lib/utils";
4
4
  import { useIsMobile } from "@/hooks/use-is-mobile";
@@ -52,14 +52,18 @@ export function ChatScrollNav({ scrollElement, userIndices, scrollToBottom }: {
52
52
  const rowTop = (el: HTMLElement, row: HTMLElement, elTop: number) =>
53
53
  row.getBoundingClientRect().top - elTop + el.scrollTop;
54
54
 
55
- const userRows = useCallback((el: HTMLElement) =>
56
- Array.from(el.querySelectorAll<HTMLElement>("[data-msg-index]"))
57
- .filter((r) => userIndices.includes(Number(r.dataset.msgIndex))),
58
- [userIndices]);
55
+ const userRows = useCallback((el: HTMLElement) => {
56
+ const users = new Set(userIndices);
57
+ return Array.from(el.querySelectorAll<HTMLElement>("[data-msg-index]"))
58
+ .filter((r) => users.has(Number(r.dataset.msgIndex)));
59
+ }, [userIndices]);
59
60
 
60
- useEffect(() => {
61
+ // Followed only while the cluster is open, since nothing at rest shows it. A streaming answer
62
+ // scrolls the transcript on every flush, and each scroll measured every user row; a layout
63
+ // effect so opening shows the real position, not the last one.
64
+ useLayoutEffect(() => {
61
65
  const el = scrollElement;
62
- if (!el) return;
66
+ if (!el || !open) return;
63
67
  let raf = 0;
64
68
  const recompute = () => {
65
69
  const top = el.scrollTop;
@@ -91,7 +95,7 @@ export function ChatScrollNav({ scrollElement, userIndices, scrollToBottom }: {
91
95
  el.removeEventListener("scroll", schedule);
92
96
  ro.disconnect();
93
97
  };
94
- }, [scrollElement, userRows]);
98
+ }, [scrollElement, userRows, open]);
95
99
 
96
100
  // Auto-collapse. Restarts on every jump so a burst of navigation keeps it open.
97
101
  useEffect(() => {
@@ -9,6 +9,7 @@ import { splitAttachmentMarkers } from "@/lib/attachment-marker-split";
9
9
  import type { ChatAttemptEvent } from "@/lib/chat-attempt-lifecycle";
10
10
  import { useChat } from "@/hooks/use-chat";
11
11
  import { useChatPrewarm } from "@/hooks/use-chat-prewarm";
12
+ import { useEffortStatus } from "@/hooks/use-effort-status";
12
13
  import { useUsage } from "@/hooks/use-usage";
13
14
  import { useDesignSessionRedirect } from "@/hooks/use-design-session-redirect";
14
15
  import { useTabStore } from "@/stores/tab-store";
@@ -26,9 +27,8 @@ import { AgentSessionProvider, normalizeProviderId } from "./agent-session-conte
26
27
  import { McpSignInBar } from "@/components/mcp-auth/mcp-sign-in-bar";
27
28
  import { SessionChangesBar } from "./session-changes-bar";
28
29
  import { CONTINUE_AFTER_STOP, TurnStopBar } from "./turn-stop-bar";
29
- import { SessionChangesContext, type SessionChangesValue } from "./session-changes-context";
30
+ import { SessionChangesContext, useSessionChangesValue } from "./session-changes-context";
30
31
  import { useSessionFileChanges } from "@/hooks/use-session-file-changes";
31
- import { openSessionReview } from "@/lib/session-file-changes-client";
32
32
  import { useTeamActivityFeed } from "@/hooks/use-team-activity-feed";
33
33
  import { MessageInput, type ChatAttachment, type MessagePriority } from "./message-input";
34
34
  import { SlashCommandPicker, type SlashItem } from "./slash-command-picker";
@@ -458,6 +458,7 @@ function ChatTabContent({ metadata, tabId, onNewSession, onFork, historyFilter }
458
458
  setModel,
459
459
  effort,
460
460
  setEffort,
461
+ effortStatus: sessionEffortStatus,
461
462
  thinking,
462
463
  setThinking,
463
464
  turnSettings,
@@ -481,17 +482,19 @@ function ChatTabContent({ metadata, tabId, onNewSession, onFork, historyFilter }
481
482
  refetchMessages();
482
483
  }, [isConnected, reconnect, refetchMessages]);
483
484
 
485
+ const effortStatus = useEffortStatus({
486
+ projectName,
487
+ // Not while the provider is still a guess: the answer would be about another provider.
488
+ providerId: preparation.pending ? undefined : preparation.providerId ?? providerId,
489
+ model,
490
+ sessionStatus: sessionEffortStatus,
491
+ });
492
+
484
493
  const sessionChanges = useSessionFileChanges({ projectName, sessionId, messages, isStreaming });
485
- const openReview = useCallback((path?: string) => {
486
- if (!sessionId) return;
487
- openSessionReview({ projectName, sessionId, title: sessionTitle || undefined, providerId, paths: sessionChanges.paths, selectPath: path });
488
- }, [projectName, sessionId, sessionTitle, providerId, sessionChanges.paths]);
489
494
  // Each turn's change tray answers the same list the bar shows.
490
- const sessionChangesValue = useMemo((): SessionChangesValue | null => (
491
- projectName && sessionId
492
- ? { projectName, sessionId, files: sessionChanges.files, refresh: sessionChanges.refresh, openReview }
493
- : null
494
- ), [projectName, sessionId, sessionChanges.files, sessionChanges.refresh, openReview]);
495
+ const { value: sessionChangesValue, openReview } = useSessionChangesValue({
496
+ projectName, sessionId, title: sessionTitle || undefined, providerId, changes: sessionChanges,
497
+ });
495
498
 
496
499
  // `model`/`effort`/`thinking` are what change when a pick does; the picks sent are read
497
500
  // through `turnSettings`, which leaves out whatever the user has not chosen.
@@ -1492,6 +1495,7 @@ function ChatTabContent({ metadata, tabId, onNewSession, onFork, historyFilter }
1492
1495
  model={model}
1493
1496
  onModelChange={!preparation?.pending && !firstSendPending ? setModel : undefined}
1494
1497
  effort={effort}
1498
+ effortStatus={effortStatus}
1495
1499
  onEffortChange={setEffort}
1496
1500
  thinking={thinking}
1497
1501
  onThinkingChange={setThinking}
@@ -3,16 +3,33 @@ import { Copy, Check } from "@/lib/icons";
3
3
  import { cn } from "@/lib/utils";
4
4
  import { formatRelativeDate } from "@/lib/format-date";
5
5
  import { copyToClipboard } from "@/lib/clipboard";
6
+ import { useRelativeTimeTick } from "@/hooks/use-relative-time-tick";
7
+
8
+ // The fields `toLocaleString()` prints, in one formatter: a long chat draws hundreds of these.
9
+ let fullDateFormat: Intl.DateTimeFormat | undefined;
6
10
 
7
11
  /** Full timestamp for the relative-time tooltip. */
8
12
  function fullDate(iso: string): string {
9
13
  try {
10
- return new Date(iso).toLocaleString();
14
+ fullDateFormat ??= new Intl.DateTimeFormat(undefined, {
15
+ year: "numeric", month: "numeric", day: "numeric", hour: "numeric", minute: "numeric", second: "numeric",
16
+ });
17
+ return fullDateFormat.format(new Date(iso));
11
18
  } catch {
12
19
  return "";
13
20
  }
14
21
  }
15
22
 
23
+ /** The relative time, kept current on its own so the message around it is never redrawn for it. */
24
+ function RelativeTime({ timestamp }: { timestamp: string }) {
25
+ useRelativeTimeTick();
26
+ return (
27
+ <time title={fullDate(timestamp)} className="pr-1 tabular-nums">
28
+ {formatRelativeDate(timestamp)}
29
+ </time>
30
+ );
31
+ }
32
+
16
33
  /**
17
34
  * Ghost-pill action button (icon + label) for the message action bar.
18
35
  * 44px min touch target on mobile; tighter on desktop. Always visible.
@@ -84,9 +101,7 @@ export function MessageActionBar({
84
101
  className,
85
102
  )}
86
103
  >
87
- <time title={fullDate(timestamp)} className="pr-1 tabular-nums">
88
- {formatRelativeDate(timestamp)}
89
- </time>
104
+ <RelativeTime timestamp={timestamp} />
90
105
  <CopyButton content={content} />
91
106
  {children}
92
107
  </div>
@@ -27,6 +27,7 @@ import { ReplyCard } from "./reply-card";
27
27
  import type { ReplyReference } from "../../../shared/chat-reply";
28
28
  import { PromptCacheChip } from "./prompt-cache-chip";
29
29
  import type { PromptCacheState } from "../../../shared/prompt-cache-idle";
30
+ import type { EffortStatus } from "../../../shared/effort-status";
30
31
 
31
32
  /**
32
33
  * Base64 payload for an image file, or undefined when it cannot be read.
@@ -144,6 +145,8 @@ interface MessageInputProps {
144
145
  onModelChange?: (model: string) => void;
145
146
  /** Current per-session effort (null = provider default) */
146
147
  effort?: string | null;
148
+ /** What overrides the effort pick outside PPM (CLAUDE_CODE_EFFORT_LEVEL, a settings cap, the model) */
149
+ effortStatus?: EffortStatus | null;
147
150
  /** Effort change handler */
148
151
  onEffortChange?: (effort: string) => void;
149
152
  /** Current per-session thinking on/off */
@@ -191,6 +194,7 @@ export const MessageInput = memo(function MessageInput({
191
194
  model,
192
195
  onModelChange,
193
196
  effort,
197
+ effortStatus,
194
198
  onEffortChange,
195
199
  thinking,
196
200
  onThinkingChange,
@@ -1048,6 +1052,7 @@ export const MessageInput = memo(function MessageInput({
1048
1052
  <ModelThinkingSelector
1049
1053
  model={model ?? null}
1050
1054
  effort={effort ?? null}
1055
+ effortStatus={effortStatus}
1051
1056
  thinking={thinking ?? false}
1052
1057
  onModelChange={onModelChange}
1053
1058
  onEffortChange={onEffortChange ?? (() => {})}
@@ -1152,6 +1157,7 @@ export const MessageInput = memo(function MessageInput({
1152
1157
  <ModelThinkingSelector
1153
1158
  model={model ?? null}
1154
1159
  effort={effort ?? null}
1160
+ effortStatus={effortStatus}
1155
1161
  thinking={thinking ?? false}
1156
1162
  onModelChange={onModelChange}
1157
1163
  onEffortChange={onEffortChange ?? (() => {})}