@hienlh/ppm 0.23.21 → 0.23.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (497) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/CLAUDE.md +6 -3
  3. package/README.md +1 -1
  4. package/assets/skills/ppm/SKILL.md +1 -1
  5. package/assets/skills/ppm/references/cli-reference.md +4 -4
  6. package/assets/skills/ppm/references/http-api.md +2 -1
  7. package/dist/web/assets/{accounts-settings-section-DD88DRdx.js → accounts-settings-section-Cg-LncY6.js} +1 -1
  8. package/dist/web/assets/accounts-settings-section-Cg-LncY6.js.br +0 -0
  9. package/dist/web/assets/accounts-settings-section-Cg-LncY6.js.gz +0 -0
  10. package/dist/web/assets/{agent-session-sheet-C0PhhLKR.js → agent-session-sheet-DMdV8GOu.js} +2 -2
  11. package/dist/web/assets/agent-session-sheet-DMdV8GOu.js.br +0 -0
  12. package/dist/web/assets/agent-session-sheet-DMdV8GOu.js.gz +0 -0
  13. package/dist/web/assets/{agent-session-window-content-D0XZgh4U.js → agent-session-window-content-CYg58J-V.js} +1 -1
  14. package/dist/web/assets/agent-session-window-content-CYg58J-V.js.br +0 -0
  15. package/dist/web/assets/agent-session-window-content-CYg58J-V.js.gz +0 -0
  16. package/dist/web/assets/{ai-resource-editor-CpfQB6A7.js → ai-resource-editor-BvEnn0Z_.js} +1 -1
  17. package/dist/web/assets/ai-resource-editor-BvEnn0Z_.js.br +0 -0
  18. package/dist/web/assets/ai-resource-editor-BvEnn0Z_.js.gz +0 -0
  19. package/dist/web/assets/{ai-settings-section-DqE67XNC.js → ai-settings-section-Bo-nf2rY.js} +1 -1
  20. package/dist/web/assets/ai-settings-section-DAqiu_11.js +1 -0
  21. package/dist/web/assets/ai-settings-section-DAqiu_11.js.br +0 -0
  22. package/dist/web/assets/ai-settings-section-DAqiu_11.js.gz +0 -0
  23. package/dist/web/assets/{android-tab-4Z4AgHz1.js → android-tab-C-wmlFc0.js} +1 -1
  24. package/dist/web/assets/android-tab-C-wmlFc0.js.br +0 -0
  25. package/dist/web/assets/android-tab-C-wmlFc0.js.gz +0 -0
  26. package/dist/web/assets/{appearance-settings-section-DjQjiJF7.js → appearance-settings-section-CYqX6BGh.js} +1 -1
  27. package/dist/web/assets/appearance-settings-section-CYqX6BGh.js.br +0 -0
  28. package/dist/web/assets/appearance-settings-section-CYqX6BGh.js.gz +0 -0
  29. package/dist/web/assets/{apps-panel-_lxY9xbc.js → apps-panel-D1VTBxgC.js} +1 -1
  30. package/dist/web/assets/apps-panel-D1VTBxgC.js.br +0 -0
  31. package/dist/web/assets/apps-panel-D1VTBxgC.js.gz +0 -0
  32. package/dist/web/assets/{branch-review-tab-BzrhnFSs.js → branch-review-tab-BG6vxSsC.js} +1 -1
  33. package/dist/web/assets/branch-review-tab-BG6vxSsC.js.br +0 -0
  34. package/dist/web/assets/branch-review-tab-BG6vxSsC.js.gz +0 -0
  35. package/dist/web/assets/chat-tab-BAwGLGyR.js +19 -0
  36. package/dist/web/assets/chat-tab-BAwGLGyR.js.br +0 -0
  37. package/dist/web/assets/chat-tab-BAwGLGyR.js.gz +0 -0
  38. package/dist/web/assets/{chat-tab-BKHepkYi.js → chat-tab-BJ0Mi2sd.js} +1 -1
  39. package/dist/web/assets/chat-tab-BJ0Mi2sd.js.br +0 -0
  40. package/dist/web/assets/chat-tab-BJ0Mi2sd.js.gz +0 -0
  41. package/dist/web/assets/{code-editor-COWmNXO4.js → code-editor-DuwbwTpM.js} +4 -4
  42. package/dist/web/assets/code-editor-DuwbwTpM.js.br +0 -0
  43. package/dist/web/assets/code-editor-DuwbwTpM.js.gz +0 -0
  44. package/dist/web/assets/{codex-reset-credit-button-BHt4yJet.js → codex-reset-credit-button-tIwfnf2G.js} +1 -1
  45. package/dist/web/assets/codex-reset-credit-button-tIwfnf2G.js.br +0 -0
  46. package/dist/web/assets/codex-reset-credit-button-tIwfnf2G.js.gz +0 -0
  47. package/dist/web/assets/{conflict-editor-tj-1gzKr.js → conflict-editor-D2n-hQqs.js} +1 -1
  48. package/dist/web/assets/conflict-editor-D2n-hQqs.js.br +0 -0
  49. package/dist/web/assets/conflict-editor-D2n-hQqs.js.gz +0 -0
  50. package/dist/web/assets/{connection-form-tab-BV2QX8Cf.js → connection-form-tab-DYqPNq_-.js} +1 -1
  51. package/dist/web/assets/connection-form-tab-DYqPNq_-.js.br +0 -0
  52. package/dist/web/assets/connection-form-tab-DYqPNq_-.js.gz +0 -0
  53. package/dist/web/assets/{csv-preview-xhE2esgz.js → csv-preview-Xh3Z6BS5.js} +1 -1
  54. package/dist/web/assets/csv-preview-Xh3Z6BS5.js.br +0 -0
  55. package/dist/web/assets/csv-preview-Xh3Z6BS5.js.gz +0 -0
  56. package/dist/web/assets/{db-tab-parts-C3MsjOID.js → db-tab-parts-Dqnb3GCP.js} +1 -1
  57. package/dist/web/assets/db-tab-parts-Dqnb3GCP.js.br +0 -0
  58. package/dist/web/assets/db-tab-parts-Dqnb3GCP.js.gz +0 -0
  59. package/dist/web/assets/{design-settings-section-C3LxYhIw.js → design-settings-section-DNNWgxF9.js} +1 -1
  60. package/dist/web/assets/design-settings-section-DNNWgxF9.js.br +0 -0
  61. package/dist/web/assets/design-settings-section-DNNWgxF9.js.gz +0 -0
  62. package/dist/web/assets/{design-tab-DYcvjdfb.js → design-tab-BoAdSpZZ.js} +1 -1
  63. package/dist/web/assets/design-tab-BoAdSpZZ.js.br +0 -0
  64. package/dist/web/assets/design-tab-BoAdSpZZ.js.gz +0 -0
  65. package/dist/web/assets/{designs-sidebar-panel-Dpr4PPFT.js → designs-sidebar-panel-BKw7HFAz.js} +1 -1
  66. package/dist/web/assets/designs-sidebar-panel-BKw7HFAz.js.br +0 -0
  67. package/dist/web/assets/designs-sidebar-panel-BKw7HFAz.js.gz +0 -0
  68. package/dist/web/assets/{diff-viewer-Fl_4jqr6.js → diff-viewer-CRmfCAZX.js} +1 -1
  69. package/dist/web/assets/diff-viewer-CRmfCAZX.js.br +0 -0
  70. package/dist/web/assets/diff-viewer-CRmfCAZX.js.gz +0 -0
  71. package/dist/web/assets/{docx-preview-bVE9shPX.js → docx-preview-zIrtL09u.js} +1 -1
  72. package/dist/web/assets/docx-preview-zIrtL09u.js.br +0 -0
  73. package/dist/web/assets/docx-preview-zIrtL09u.js.gz +0 -0
  74. package/dist/web/assets/{editor-dialog-eEOYWdbr.js → editor-dialog-jMYvXVla.js} +1 -1
  75. package/dist/web/assets/editor-dialog-jMYvXVla.js.br +0 -0
  76. package/dist/web/assets/editor-dialog-jMYvXVla.js.gz +0 -0
  77. package/dist/web/assets/{editor-language-service-D1_Ggq4s.js → editor-language-service-Yb-61Bro.js} +1 -1
  78. package/dist/web/assets/editor-language-service-Yb-61Bro.js.br +0 -0
  79. package/dist/web/assets/editor-language-service-Yb-61Bro.js.gz +0 -0
  80. package/dist/web/assets/{explorer-body-C0UOcBPJ.js → explorer-body-C6qKehmd.js} +1 -1
  81. package/dist/web/assets/explorer-body-C6qKehmd.js.br +0 -0
  82. package/dist/web/assets/explorer-body-C6qKehmd.js.gz +0 -0
  83. package/dist/web/assets/{explorer-window-content-Dxee7SuJ.js → explorer-window-content-B9P8grrY.js} +1 -1
  84. package/dist/web/assets/explorer-window-content-B9P8grrY.js.br +0 -0
  85. package/dist/web/assets/explorer-window-content-B9P8grrY.js.gz +0 -0
  86. package/dist/web/assets/{extension-manager-section-D8krnoj8.js → extension-manager-section-C45v1QtG.js} +1 -1
  87. package/dist/web/assets/extension-manager-section-C45v1QtG.js.br +0 -0
  88. package/dist/web/assets/extension-manager-section-C45v1QtG.js.gz +0 -0
  89. package/dist/web/assets/{extension-webview-CtIdV69T.js → extension-webview-DIxM-xkH.js} +1 -1
  90. package/dist/web/assets/extension-webview-DIxM-xkH.js.br +0 -0
  91. package/dist/web/assets/extension-webview-DIxM-xkH.js.gz +0 -0
  92. package/dist/web/assets/{files-settings-section-C8vtarR8.js → files-settings-section-DeVlzKXd.js} +1 -1
  93. package/dist/web/assets/files-settings-section-DeVlzKXd.js.br +0 -0
  94. package/dist/web/assets/files-settings-section-DeVlzKXd.js.gz +0 -0
  95. package/dist/web/assets/format-date-CkyHzZLA.js +1 -0
  96. package/dist/web/assets/{general-settings-section-Chu8Ut8h.js → general-settings-section-CLVDIEym.js} +1 -1
  97. package/dist/web/assets/general-settings-section-CLVDIEym.js.br +0 -0
  98. package/dist/web/assets/general-settings-section-CLVDIEym.js.gz +0 -0
  99. package/dist/web/assets/{git-log-panel-DG09p6k5.js → git-log-panel-D3Q9NRll.js} +1 -1
  100. package/dist/web/assets/git-log-panel-D3Q9NRll.js.br +0 -0
  101. package/dist/web/assets/git-log-panel-D3Q9NRll.js.gz +0 -0
  102. package/dist/web/assets/{git-review-tab-CSy8DjSC.js → git-review-tab-ORgiYbva.js} +1 -1
  103. package/dist/web/assets/git-review-tab-ORgiYbva.js.br +0 -0
  104. package/dist/web/assets/git-review-tab-ORgiYbva.js.gz +0 -0
  105. package/dist/web/assets/{glide-data-grid-BN1y4BT0.js → glide-data-grid-CtWl1BTk.js} +1 -1
  106. package/dist/web/assets/glide-data-grid-CtWl1BTk.js.br +0 -0
  107. package/dist/web/assets/glide-data-grid-CtWl1BTk.js.gz +0 -0
  108. package/dist/web/assets/{grid-save-dialog-CfSuWw2t.js → grid-save-dialog-eV25Mqpg.js} +1 -1
  109. package/dist/web/assets/grid-save-dialog-eV25Mqpg.js.br +0 -0
  110. package/dist/web/assets/grid-save-dialog-eV25Mqpg.js.gz +0 -0
  111. package/dist/web/assets/{group-chat-tab-B04QMp7A.js → group-chat-tab-CYmbYXN2.js} +1 -1
  112. package/dist/web/assets/group-chat-tab-CYmbYXN2.js.br +0 -0
  113. package/dist/web/assets/group-chat-tab-CYmbYXN2.js.gz +0 -0
  114. package/dist/web/assets/{impexp-tab-BRNouquR.js → impexp-tab-D2OKxpFq.js} +1 -1
  115. package/dist/web/assets/impexp-tab-D2OKxpFq.js.br +0 -0
  116. package/dist/web/assets/impexp-tab-D2OKxpFq.js.gz +0 -0
  117. package/dist/web/assets/{index-BpgSIM5B.js → index-D8QDpdgs.js} +7 -7
  118. package/dist/web/assets/index-D8QDpdgs.js.br +0 -0
  119. package/dist/web/assets/index-D8QDpdgs.js.gz +0 -0
  120. package/dist/web/assets/{jira-watcher-section-CJeS5r6D.js → jira-watcher-section-B3OaMlim.js} +1 -1
  121. package/dist/web/assets/{keybindings-store-Dvxq-vZD.js → keybindings-store-C_aRhBu4.js} +1 -1
  122. package/dist/web/assets/{keyboard-shortcuts-section-BKDyZHhK.js → keyboard-shortcuts-section-DXRyKrSy.js} +1 -1
  123. package/dist/web/assets/keyboard-shortcuts-section-DXRyKrSy.js.br +0 -0
  124. package/dist/web/assets/keyboard-shortcuts-section-DXRyKrSy.js.gz +0 -0
  125. package/dist/web/assets/{language-servers-section-CbbkezhA.js → language-servers-section-BTs9iuZc.js} +2 -2
  126. package/dist/web/assets/language-servers-section-BTs9iuZc.js.br +0 -0
  127. package/dist/web/assets/language-servers-section-BTs9iuZc.js.gz +0 -0
  128. package/dist/web/assets/{logs-app-Blr1-v4q.js → logs-app-BlhVTOYN.js} +1 -1
  129. package/dist/web/assets/logs-app-BlhVTOYN.js.br +0 -0
  130. package/dist/web/assets/logs-app-BlhVTOYN.js.gz +0 -0
  131. package/dist/web/assets/{logs-tab-CGPtn2tU.js → logs-tab-kqNARbz0.js} +1 -1
  132. package/dist/web/assets/{logs-window-content-DAczcNmh.js → logs-window-content-UUCJJCqG.js} +1 -1
  133. package/dist/web/assets/{lsp-client-Ds3U2O52.js → lsp-client-BTlK_8lb.js} +1 -1
  134. package/dist/web/assets/lsp-client-BTlK_8lb.js.br +0 -0
  135. package/dist/web/assets/lsp-client-BTlK_8lb.js.gz +0 -0
  136. package/dist/web/assets/{markdown-renderer-DbJVwfUH.js → markdown-renderer-ClGxDien.js} +1 -1
  137. package/dist/web/assets/{markdown-renderer-DzCPadpB.js → markdown-renderer-DpsToFFB.js} +1 -1
  138. package/dist/web/assets/markdown-renderer-DpsToFFB.js.br +0 -0
  139. package/dist/web/assets/markdown-renderer-DpsToFFB.js.gz +0 -0
  140. package/dist/web/assets/{mobile-explorer-sheet-BClCMXb8.js → mobile-explorer-sheet-CybJbScN.js} +1 -1
  141. package/dist/web/assets/mobile-explorer-sheet-CybJbScN.js.br +0 -0
  142. package/dist/web/assets/mobile-explorer-sheet-CybJbScN.js.gz +0 -0
  143. package/dist/web/assets/{new-design-dialog-HRP9H11v.js → new-design-dialog-9WTgcJoj.js} +1 -1
  144. package/dist/web/assets/new-design-dialog-9WTgcJoj.js.br +0 -0
  145. package/dist/web/assets/new-design-dialog-9WTgcJoj.js.gz +0 -0
  146. package/dist/web/assets/{notification-store-BomzYNQj.js → notification-store-CPVq5ec5.js} +1 -1
  147. package/dist/web/assets/notifications-settings-section-xrYII-pL.js +1 -0
  148. package/dist/web/assets/notifications-settings-section-xrYII-pL.js.br +0 -0
  149. package/dist/web/assets/notifications-settings-section-xrYII-pL.js.gz +0 -0
  150. package/dist/web/assets/{ppmbot-settings-section-BRowIIqc.js → ppmbot-settings-section-Bc46iDP9.js} +1 -1
  151. package/dist/web/assets/ppmbot-settings-section-Bc46iDP9.js.br +0 -0
  152. package/dist/web/assets/ppmbot-settings-section-Bc46iDP9.js.gz +0 -0
  153. package/dist/web/assets/{problems-panel-DdB10Ybd.js → problems-panel-D49-Xkzd.js} +1 -1
  154. package/dist/web/assets/problems-panel-D49-Xkzd.js.br +0 -0
  155. package/dist/web/assets/problems-panel-D49-Xkzd.js.gz +0 -0
  156. package/dist/web/assets/{process-table-CzLaWPe0.js → process-table-NxYTZ0n9.js} +1 -1
  157. package/dist/web/assets/process-table-NxYTZ0n9.js.br +0 -0
  158. package/dist/web/assets/process-table-NxYTZ0n9.js.gz +0 -0
  159. package/dist/web/assets/{proxy-settings-section-BCq7R5DA.js → proxy-settings-section-Exx7cQc4.js} +1 -1
  160. package/dist/web/assets/proxy-settings-section-Exx7cQc4.js.br +0 -0
  161. package/dist/web/assets/proxy-settings-section-Exx7cQc4.js.gz +0 -0
  162. package/dist/web/assets/{query-tab-DGvj_h6l.js → query-tab-BWtJPuxw.js} +1 -1
  163. package/dist/web/assets/query-tab-BWtJPuxw.js.br +0 -0
  164. package/dist/web/assets/{query-tab-DGvj_h6l.js.gz → query-tab-BWtJPuxw.js.gz} +0 -0
  165. package/dist/web/assets/{remote-access-settings-section-BrqOoOyk.js → remote-access-settings-section-C4K__DNW.js} +1 -1
  166. package/dist/web/assets/remote-access-settings-section-C4K__DNW.js.br +0 -0
  167. package/dist/web/assets/remote-access-settings-section-C4K__DNW.js.gz +0 -0
  168. package/dist/web/assets/{remote-desktop-clipboard-notice-5r6UbdlN.js → remote-desktop-clipboard-notice-DQyysBwL.js} +1 -1
  169. package/dist/web/assets/remote-desktop-clipboard-notice-DQyysBwL.js.br +0 -0
  170. package/dist/web/assets/remote-desktop-clipboard-notice-DQyysBwL.js.gz +0 -0
  171. package/dist/web/assets/{remote-desktop-mobile-sheet-B9Q4auqe.js → remote-desktop-mobile-sheet-DZtEFmgf.js} +2 -2
  172. package/dist/web/assets/remote-desktop-mobile-sheet-DZtEFmgf.js.br +0 -0
  173. package/dist/web/assets/remote-desktop-mobile-sheet-DZtEFmgf.js.gz +0 -0
  174. package/dist/web/assets/{remote-desktop-mobile-view-CL-jrkA8.js → remote-desktop-mobile-view-VW7HdPUX.js} +1 -1
  175. package/dist/web/assets/remote-desktop-mobile-view-VW7HdPUX.js.br +0 -0
  176. package/dist/web/assets/remote-desktop-mobile-view-VW7HdPUX.js.gz +0 -0
  177. package/dist/web/assets/{remote-desktop-readiness-gate-DXEf2Jnz.js → remote-desktop-readiness-gate-B3tuP9Kt.js} +1 -1
  178. package/dist/web/assets/remote-desktop-readiness-gate-B3tuP9Kt.js.br +0 -0
  179. package/dist/web/assets/remote-desktop-readiness-gate-B3tuP9Kt.js.gz +0 -0
  180. package/dist/web/assets/{remote-desktop-window-content-20P6-gKB.js → remote-desktop-window-content-n0NoDCaE.js} +1 -1
  181. package/dist/web/assets/remote-desktop-window-content-n0NoDCaE.js.br +0 -0
  182. package/dist/web/assets/remote-desktop-window-content-n0NoDCaE.js.gz +0 -0
  183. package/dist/web/assets/{rename-object-dialog-Bs7pUsUP.js → rename-object-dialog-rLTW4A55.js} +1 -1
  184. package/dist/web/assets/rename-object-dialog-rLTW4A55.js.br +0 -0
  185. package/dist/web/assets/rename-object-dialog-rLTW4A55.js.gz +0 -0
  186. package/dist/web/assets/{review-code-Bjo4sHN9.js → review-code-DNfsaNO1.js} +1 -1
  187. package/dist/web/assets/review-code-DNfsaNO1.js.br +0 -0
  188. package/dist/web/assets/review-code-DNfsaNO1.js.gz +0 -0
  189. package/dist/web/assets/{save-as-dialog-BHx0Wp2B.js → save-as-dialog-BSz8WrNZ.js} +1 -1
  190. package/dist/web/assets/save-as-dialog-BSz8WrNZ.js.br +0 -0
  191. package/dist/web/assets/save-as-dialog-BSz8WrNZ.js.gz +0 -0
  192. package/dist/web/assets/{schedules-settings-section-C6ATYDPK.js → schedules-settings-section-DqUZ-H2K.js} +1 -1
  193. package/dist/web/assets/schedules-settings-section-DqUZ-H2K.js.br +0 -0
  194. package/dist/web/assets/schedules-settings-section-DqUZ-H2K.js.gz +0 -0
  195. package/dist/web/assets/{session-review-tab-BPMUyNTx.js → session-review-tab-DKtrFb9y.js} +1 -1
  196. package/dist/web/assets/session-review-tab-DKtrFb9y.js.br +0 -0
  197. package/dist/web/assets/session-review-tab-DKtrFb9y.js.gz +0 -0
  198. package/dist/web/assets/{settings-body-Dy5D6Vu7.js → settings-body-C42xA9Dw.js} +1 -1
  199. package/dist/web/assets/settings-body-C42xA9Dw.js.br +0 -0
  200. package/dist/web/assets/settings-body-C42xA9Dw.js.gz +0 -0
  201. package/dist/web/assets/{settings-rows-BRoYE2To.js → settings-rows-C7ZatT9O.js} +1 -1
  202. package/dist/web/assets/settings-rows-C7ZatT9O.js.br +0 -0
  203. package/dist/web/assets/settings-rows-C7ZatT9O.js.gz +0 -0
  204. package/dist/web/assets/{settings-section-content-essUViKu.js → settings-section-content-B_iXJpcK.js} +2 -2
  205. package/dist/web/assets/settings-section-content-B_iXJpcK.js.br +0 -0
  206. package/dist/web/assets/settings-section-content-B_iXJpcK.js.gz +0 -0
  207. package/dist/web/assets/settings-section-content-rxfQEQ66.js +1 -0
  208. package/dist/web/assets/{settings-tab-Di1DUP1j.js → settings-tab-PRw6OpEb.js} +1 -1
  209. package/dist/web/assets/{settings-window-content-DYvYFPWy.js → settings-window-content-WITQsGfW.js} +1 -1
  210. package/dist/web/assets/{slash-command-picker-8da30mYP.js → slash-command-picker-DmDs8nOj.js} +1 -1
  211. package/dist/web/assets/slash-command-picker-DmDs8nOj.js.br +0 -0
  212. package/dist/web/assets/slash-command-picker-DmDs8nOj.js.gz +0 -0
  213. package/dist/web/assets/{sql-object-tab-DRIHVXuR.js → sql-object-tab-D6P4hzkb.js} +1 -1
  214. package/dist/web/assets/sql-object-tab-D6P4hzkb.js.br +0 -0
  215. package/dist/web/assets/sql-object-tab-D6P4hzkb.js.gz +0 -0
  216. package/dist/web/assets/{sqlite-viewer-G-Jl2yEr.js → sqlite-viewer-mUitnzHB.js} +1 -1
  217. package/dist/web/assets/sqlite-viewer-mUitnzHB.js.br +0 -0
  218. package/dist/web/assets/sqlite-viewer-mUitnzHB.js.gz +0 -0
  219. package/dist/web/assets/{structure-save-dialog-Cwikz0we.js → structure-save-dialog-CmbNlmBY.js} +1 -1
  220. package/dist/web/assets/structure-save-dialog-CmbNlmBY.js.br +0 -0
  221. package/dist/web/assets/structure-save-dialog-CmbNlmBY.js.gz +0 -0
  222. package/dist/web/assets/{structure-tab-CWEgdZPm.js → structure-tab-DCPNuFTe.js} +1 -1
  223. package/dist/web/assets/structure-tab-DCPNuFTe.js.br +0 -0
  224. package/dist/web/assets/structure-tab-DCPNuFTe.js.gz +0 -0
  225. package/dist/web/assets/{system-monitor-body-BDQm9-wb.js → system-monitor-body-BhxyqSqu.js} +2 -2
  226. package/dist/web/assets/system-monitor-body-BhxyqSqu.js.br +0 -0
  227. package/dist/web/assets/system-monitor-body-BhxyqSqu.js.gz +0 -0
  228. package/dist/web/assets/{system-monitor-tab-BouN21Yh.js → system-monitor-tab-B_miogQI.js} +1 -1
  229. package/dist/web/assets/{system-monitor-window-content-BRyWBSOb.js → system-monitor-window-content-8IogR6tR.js} +1 -1
  230. package/dist/web/assets/{tab-host-window-content-CZ55-bTz.js → tab-host-window-content-BWHQjgWZ.js} +1 -1
  231. package/dist/web/assets/tab-host-window-content-BWHQjgWZ.js.br +0 -0
  232. package/dist/web/assets/tab-host-window-content-BWHQjgWZ.js.gz +0 -0
  233. package/dist/web/assets/{table-tab-Bmn4UdBV.js → table-tab-E3YMueu_.js} +1 -1
  234. package/dist/web/assets/table-tab-E3YMueu_.js.br +0 -0
  235. package/dist/web/assets/table-tab-E3YMueu_.js.gz +0 -0
  236. package/dist/web/assets/{telegram-bot-token-field-fdHwRf39.js → telegram-bot-token-field-CwW24m62.js} +1 -1
  237. package/dist/web/assets/telegram-bot-token-field-CwW24m62.js.br +0 -0
  238. package/dist/web/assets/telegram-bot-token-field-CwW24m62.js.gz +0 -0
  239. package/dist/web/assets/{terminal-tab-D-qlZXLK.js → terminal-tab-BAe5cWEL.js} +1 -1
  240. package/dist/web/assets/terminal-tab-BAe5cWEL.js.br +0 -0
  241. package/dist/web/assets/terminal-tab-BAe5cWEL.js.gz +0 -0
  242. package/dist/web/assets/{tool-cards-BdXbQ9Fv.js → tool-cards-DrCiSH2r.js} +2 -2
  243. package/dist/web/assets/tool-cards-DrCiSH2r.js.br +0 -0
  244. package/dist/web/assets/tool-cards-DrCiSH2r.js.gz +0 -0
  245. package/dist/web/assets/{tools-settings-section-CcPaspbR.js → tools-settings-section-S-vBr2af.js} +1 -1
  246. package/dist/web/assets/tools-settings-section-S-vBr2af.js.br +0 -0
  247. package/dist/web/assets/tools-settings-section-S-vBr2af.js.gz +0 -0
  248. package/dist/web/assets/{use-inline-blame-Ck5XoSS3.js → use-inline-blame-BzVaaCCt.js} +1 -1
  249. package/dist/web/assets/use-inline-blame-BzVaaCCt.js.br +0 -0
  250. package/dist/web/assets/{use-inline-blame-Ck5XoSS3.js.gz → use-inline-blame-BzVaaCCt.js.gz} +0 -0
  251. package/dist/web/assets/{use-lsp-CGSqfFBk.js → use-lsp-BwDL9Fqk.js} +1 -1
  252. package/dist/web/assets/use-lsp-BwDL9Fqk.js.br +0 -0
  253. package/dist/web/assets/use-lsp-BwDL9Fqk.js.gz +0 -0
  254. package/dist/web/assets/{use-monaco-theme-BW05eZS4.js → use-monaco-theme-DljSc3Xo.js} +1 -1
  255. package/dist/web/assets/{use-process-kill-DJ_f7rCO.js → use-process-kill-YDcDqDtG.js} +1 -1
  256. package/dist/web/assets/use-process-kill-YDcDqDtG.js.br +0 -0
  257. package/dist/web/assets/use-process-kill-YDcDqDtG.js.gz +0 -0
  258. package/dist/web/assets/{use-sql-schema-info-wt320fVR.js → use-sql-schema-info-cAI0vn7d.js} +1 -1
  259. package/dist/web/assets/use-sql-schema-info-cAI0vn7d.js.br +0 -0
  260. package/dist/web/assets/use-sql-schema-info-cAI0vn7d.js.gz +0 -0
  261. package/dist/web/assets/{use-websocket-CTwe74Cb.js → use-websocket-Djo_ruX_.js} +1 -1
  262. package/dist/web/assets/{web-preview-tab-D4XayUEK.js → web-preview-tab-DXy0hRef.js} +1 -1
  263. package/dist/web/assets/web-preview-tab-DXy0hRef.js.br +0 -0
  264. package/dist/web/assets/web-preview-tab-DXy0hRef.js.gz +0 -0
  265. package/dist/web/index.html +2 -2
  266. package/dist/web/index.html.br +0 -0
  267. package/dist/web/index.html.gz +0 -0
  268. package/dist/web/sw.js +1 -1
  269. package/dist/web/sw.js.br +0 -0
  270. package/dist/web/sw.js.gz +0 -0
  271. package/docs/architecture/ai-chat-and-providers.md +29 -0
  272. package/docs/deployment-guide.md +39 -0
  273. package/docs/lessons-learned.md +14 -4
  274. package/package.json +1 -1
  275. package/scripts/mem-diag-report.ts +31 -0
  276. package/src/providers/claude-agent-sdk-query-options.ts +13 -2
  277. package/src/providers/claude-agent-sdk.ts +58 -18
  278. package/src/providers/claude-approval-queue.ts +75 -0
  279. package/src/providers/claude-effort-status.ts +211 -0
  280. package/src/providers/registry.ts +10 -1
  281. package/src/server/index.ts +7 -6
  282. package/src/server/middleware/access-log.ts +3 -0
  283. package/src/server/middleware/cors-headers.ts +44 -0
  284. package/src/server/routes/chat.ts +21 -1
  285. package/src/server/routes/database-impexp.ts +1 -1
  286. package/src/server/routes/design-preview.ts +2 -2
  287. package/src/server/routes/settings.ts +16 -0
  288. package/src/server/ws/chat-effort-status.ts +73 -0
  289. package/src/server/ws/chat.ts +33 -3
  290. package/src/services/compact-segment.ts +14 -19
  291. package/src/services/database/grid-export-tickets.ts +6 -2
  292. package/src/services/database/impexp/readers/file-text.ts +6 -3
  293. package/src/services/edge-forwarder.ts +12 -0
  294. package/src/services/file-chunks.ts +39 -0
  295. package/src/services/file-lines.ts +10 -17
  296. package/src/services/lsp/lsp-manager.ts +3 -0
  297. package/src/services/memory-diagnostics-native.ts +110 -0
  298. package/src/services/memory-diagnostics-report.ts +200 -0
  299. package/src/services/memory-diagnostics-requests.ts +95 -0
  300. package/src/services/memory-diagnostics-spawns.ts +242 -0
  301. package/src/services/memory-diagnostics.ts +121 -23
  302. package/src/services/memory-watchdog.ts +185 -0
  303. package/src/services/nested-subagent-spy.ts +3 -0
  304. package/src/services/process-thread-snapshot.ts +12 -2
  305. package/src/services/process-tree-memory.ts +161 -0
  306. package/src/services/read-lines.ts +16 -27
  307. package/src/services/supervisor.ts +1 -0
  308. package/src/services/terminal.service.ts +8 -0
  309. package/src/services/transcript-images-file.ts +7 -11
  310. package/src/shared/effort-status.ts +128 -0
  311. package/src/types/api.ts +3 -1
  312. package/src/types/chat.ts +8 -0
  313. package/src/web/components/chat/chat-scroll-nav.tsx +12 -8
  314. package/src/web/components/chat/chat-tab.tsx +15 -11
  315. package/src/web/components/chat/message-action-bar.tsx +19 -4
  316. package/src/web/components/chat/message-input.tsx +6 -0
  317. package/src/web/components/chat/message-list.tsx +55 -21
  318. package/src/web/components/chat/message-user-bubble.tsx +2 -2
  319. package/src/web/components/chat/model-thinking-selector-helpers.ts +79 -0
  320. package/src/web/components/chat/model-thinking-selector.tsx +36 -10
  321. package/src/web/components/chat/session-changes-context.tsx +29 -1
  322. package/src/web/components/layout/reparenting-tab.tsx +53 -14
  323. package/src/web/components/settings/ai-settings-section.tsx +32 -0
  324. package/src/web/components/settings/notifications-settings-section.tsx +14 -1
  325. package/src/web/hooks/use-chat.ts +16 -0
  326. package/src/web/hooks/use-effort-status.ts +46 -0
  327. package/src/web/hooks/use-relative-time-tick.ts +35 -0
  328. package/src/web/hooks/use-session-file-changes.ts +5 -1
  329. package/src/web/hooks/use-tab-overflow.ts +26 -13
  330. package/src/web/lib/format-date.ts +6 -1
  331. package/src/web/lib/web-push-client.ts +35 -5
  332. package/src/web/lib/web-push-support.ts +57 -4
  333. package/dist/web/assets/accounts-settings-section-DD88DRdx.js.br +0 -0
  334. package/dist/web/assets/accounts-settings-section-DD88DRdx.js.gz +0 -0
  335. package/dist/web/assets/agent-session-sheet-C0PhhLKR.js.br +0 -0
  336. package/dist/web/assets/agent-session-sheet-C0PhhLKR.js.gz +0 -0
  337. package/dist/web/assets/agent-session-window-content-D0XZgh4U.js.br +0 -0
  338. package/dist/web/assets/agent-session-window-content-D0XZgh4U.js.gz +0 -0
  339. package/dist/web/assets/ai-resource-editor-CpfQB6A7.js.br +0 -0
  340. package/dist/web/assets/ai-resource-editor-CpfQB6A7.js.gz +0 -0
  341. package/dist/web/assets/ai-settings-section-CQWBwWcM.js +0 -1
  342. package/dist/web/assets/ai-settings-section-CQWBwWcM.js.br +0 -0
  343. package/dist/web/assets/ai-settings-section-CQWBwWcM.js.gz +0 -0
  344. package/dist/web/assets/android-tab-4Z4AgHz1.js.br +0 -0
  345. package/dist/web/assets/android-tab-4Z4AgHz1.js.gz +0 -0
  346. package/dist/web/assets/appearance-settings-section-DjQjiJF7.js.br +0 -0
  347. package/dist/web/assets/appearance-settings-section-DjQjiJF7.js.gz +0 -0
  348. package/dist/web/assets/apps-panel-_lxY9xbc.js.br +0 -0
  349. package/dist/web/assets/apps-panel-_lxY9xbc.js.gz +0 -0
  350. package/dist/web/assets/branch-review-tab-BzrhnFSs.js.br +0 -0
  351. package/dist/web/assets/branch-review-tab-BzrhnFSs.js.gz +0 -0
  352. package/dist/web/assets/chat-tab-BKHepkYi.js.br +0 -0
  353. package/dist/web/assets/chat-tab-BKHepkYi.js.gz +0 -0
  354. package/dist/web/assets/chat-tab-Cv8gYzcA.js +0 -19
  355. package/dist/web/assets/chat-tab-Cv8gYzcA.js.br +0 -0
  356. package/dist/web/assets/chat-tab-Cv8gYzcA.js.gz +0 -0
  357. package/dist/web/assets/code-editor-COWmNXO4.js.br +0 -0
  358. package/dist/web/assets/code-editor-COWmNXO4.js.gz +0 -0
  359. package/dist/web/assets/codex-reset-credit-button-BHt4yJet.js.br +0 -0
  360. package/dist/web/assets/codex-reset-credit-button-BHt4yJet.js.gz +0 -0
  361. package/dist/web/assets/conflict-editor-tj-1gzKr.js.br +0 -0
  362. package/dist/web/assets/conflict-editor-tj-1gzKr.js.gz +0 -0
  363. package/dist/web/assets/connection-form-tab-BV2QX8Cf.js.br +0 -0
  364. package/dist/web/assets/connection-form-tab-BV2QX8Cf.js.gz +0 -0
  365. package/dist/web/assets/csv-preview-xhE2esgz.js.br +0 -0
  366. package/dist/web/assets/csv-preview-xhE2esgz.js.gz +0 -0
  367. package/dist/web/assets/db-tab-parts-C3MsjOID.js.br +0 -0
  368. package/dist/web/assets/db-tab-parts-C3MsjOID.js.gz +0 -0
  369. package/dist/web/assets/design-settings-section-C3LxYhIw.js.br +0 -0
  370. package/dist/web/assets/design-settings-section-C3LxYhIw.js.gz +0 -0
  371. package/dist/web/assets/design-tab-DYcvjdfb.js.br +0 -0
  372. package/dist/web/assets/design-tab-DYcvjdfb.js.gz +0 -0
  373. package/dist/web/assets/designs-sidebar-panel-Dpr4PPFT.js.br +0 -0
  374. package/dist/web/assets/designs-sidebar-panel-Dpr4PPFT.js.gz +0 -0
  375. package/dist/web/assets/diff-viewer-Fl_4jqr6.js.br +0 -0
  376. package/dist/web/assets/diff-viewer-Fl_4jqr6.js.gz +0 -0
  377. package/dist/web/assets/docx-preview-bVE9shPX.js.br +0 -0
  378. package/dist/web/assets/docx-preview-bVE9shPX.js.gz +0 -0
  379. package/dist/web/assets/editor-dialog-eEOYWdbr.js.br +0 -0
  380. package/dist/web/assets/editor-dialog-eEOYWdbr.js.gz +0 -0
  381. package/dist/web/assets/editor-language-service-D1_Ggq4s.js.br +0 -0
  382. package/dist/web/assets/editor-language-service-D1_Ggq4s.js.gz +0 -0
  383. package/dist/web/assets/explorer-body-C0UOcBPJ.js.br +0 -0
  384. package/dist/web/assets/explorer-body-C0UOcBPJ.js.gz +0 -0
  385. package/dist/web/assets/explorer-window-content-Dxee7SuJ.js.br +0 -0
  386. package/dist/web/assets/explorer-window-content-Dxee7SuJ.js.gz +0 -0
  387. package/dist/web/assets/extension-manager-section-D8krnoj8.js.br +0 -0
  388. package/dist/web/assets/extension-manager-section-D8krnoj8.js.gz +0 -0
  389. package/dist/web/assets/extension-webview-CtIdV69T.js.br +0 -0
  390. package/dist/web/assets/extension-webview-CtIdV69T.js.gz +0 -0
  391. package/dist/web/assets/files-settings-section-C8vtarR8.js.br +0 -0
  392. package/dist/web/assets/files-settings-section-C8vtarR8.js.gz +0 -0
  393. package/dist/web/assets/format-date-CSHaU2wN.js +0 -1
  394. package/dist/web/assets/general-settings-section-Chu8Ut8h.js.br +0 -0
  395. package/dist/web/assets/general-settings-section-Chu8Ut8h.js.gz +0 -0
  396. package/dist/web/assets/git-log-panel-DG09p6k5.js.br +0 -0
  397. package/dist/web/assets/git-log-panel-DG09p6k5.js.gz +0 -0
  398. package/dist/web/assets/git-review-tab-CSy8DjSC.js.br +0 -0
  399. package/dist/web/assets/git-review-tab-CSy8DjSC.js.gz +0 -0
  400. package/dist/web/assets/glide-data-grid-BN1y4BT0.js.br +0 -0
  401. package/dist/web/assets/glide-data-grid-BN1y4BT0.js.gz +0 -0
  402. package/dist/web/assets/grid-save-dialog-CfSuWw2t.js.br +0 -0
  403. package/dist/web/assets/grid-save-dialog-CfSuWw2t.js.gz +0 -0
  404. package/dist/web/assets/group-chat-tab-B04QMp7A.js.br +0 -0
  405. package/dist/web/assets/group-chat-tab-B04QMp7A.js.gz +0 -0
  406. package/dist/web/assets/impexp-tab-BRNouquR.js.br +0 -0
  407. package/dist/web/assets/impexp-tab-BRNouquR.js.gz +0 -0
  408. package/dist/web/assets/index-BpgSIM5B.js.br +0 -0
  409. package/dist/web/assets/index-BpgSIM5B.js.gz +0 -0
  410. package/dist/web/assets/keyboard-shortcuts-section-BKDyZHhK.js.br +0 -0
  411. package/dist/web/assets/keyboard-shortcuts-section-BKDyZHhK.js.gz +0 -0
  412. package/dist/web/assets/language-servers-section-CbbkezhA.js.br +0 -0
  413. package/dist/web/assets/language-servers-section-CbbkezhA.js.gz +0 -0
  414. package/dist/web/assets/logs-app-Blr1-v4q.js.br +0 -0
  415. package/dist/web/assets/logs-app-Blr1-v4q.js.gz +0 -0
  416. package/dist/web/assets/lsp-client-Ds3U2O52.js.br +0 -0
  417. package/dist/web/assets/lsp-client-Ds3U2O52.js.gz +0 -0
  418. package/dist/web/assets/markdown-renderer-DzCPadpB.js.br +0 -0
  419. package/dist/web/assets/markdown-renderer-DzCPadpB.js.gz +0 -0
  420. package/dist/web/assets/mobile-explorer-sheet-BClCMXb8.js.br +0 -0
  421. package/dist/web/assets/mobile-explorer-sheet-BClCMXb8.js.gz +0 -0
  422. package/dist/web/assets/new-design-dialog-HRP9H11v.js.br +0 -0
  423. package/dist/web/assets/new-design-dialog-HRP9H11v.js.gz +0 -0
  424. package/dist/web/assets/notifications-settings-section-CYTTQaNr.js +0 -1
  425. package/dist/web/assets/notifications-settings-section-CYTTQaNr.js.br +0 -0
  426. package/dist/web/assets/notifications-settings-section-CYTTQaNr.js.gz +0 -0
  427. package/dist/web/assets/ppmbot-settings-section-BRowIIqc.js.br +0 -0
  428. package/dist/web/assets/ppmbot-settings-section-BRowIIqc.js.gz +0 -0
  429. package/dist/web/assets/problems-panel-DdB10Ybd.js.br +0 -0
  430. package/dist/web/assets/problems-panel-DdB10Ybd.js.gz +0 -0
  431. package/dist/web/assets/process-table-CzLaWPe0.js.br +0 -0
  432. package/dist/web/assets/process-table-CzLaWPe0.js.gz +0 -0
  433. package/dist/web/assets/proxy-settings-section-BCq7R5DA.js.br +0 -0
  434. package/dist/web/assets/proxy-settings-section-BCq7R5DA.js.gz +0 -0
  435. package/dist/web/assets/query-tab-DGvj_h6l.js.br +0 -0
  436. package/dist/web/assets/remote-access-settings-section-BrqOoOyk.js.br +0 -0
  437. package/dist/web/assets/remote-access-settings-section-BrqOoOyk.js.gz +0 -0
  438. package/dist/web/assets/remote-desktop-clipboard-notice-5r6UbdlN.js.br +0 -0
  439. package/dist/web/assets/remote-desktop-clipboard-notice-5r6UbdlN.js.gz +0 -0
  440. package/dist/web/assets/remote-desktop-mobile-sheet-B9Q4auqe.js.br +0 -0
  441. package/dist/web/assets/remote-desktop-mobile-sheet-B9Q4auqe.js.gz +0 -0
  442. package/dist/web/assets/remote-desktop-mobile-view-CL-jrkA8.js.br +0 -0
  443. package/dist/web/assets/remote-desktop-mobile-view-CL-jrkA8.js.gz +0 -0
  444. package/dist/web/assets/remote-desktop-readiness-gate-DXEf2Jnz.js.br +0 -0
  445. package/dist/web/assets/remote-desktop-readiness-gate-DXEf2Jnz.js.gz +0 -0
  446. package/dist/web/assets/remote-desktop-window-content-20P6-gKB.js.br +0 -0
  447. package/dist/web/assets/remote-desktop-window-content-20P6-gKB.js.gz +0 -0
  448. package/dist/web/assets/rename-object-dialog-Bs7pUsUP.js.br +0 -0
  449. package/dist/web/assets/rename-object-dialog-Bs7pUsUP.js.gz +0 -0
  450. package/dist/web/assets/review-code-Bjo4sHN9.js.br +0 -0
  451. package/dist/web/assets/review-code-Bjo4sHN9.js.gz +0 -0
  452. package/dist/web/assets/save-as-dialog-BHx0Wp2B.js.br +0 -0
  453. package/dist/web/assets/save-as-dialog-BHx0Wp2B.js.gz +0 -0
  454. package/dist/web/assets/schedules-settings-section-C6ATYDPK.js.br +0 -0
  455. package/dist/web/assets/schedules-settings-section-C6ATYDPK.js.gz +0 -0
  456. package/dist/web/assets/session-review-tab-BPMUyNTx.js.br +0 -0
  457. package/dist/web/assets/session-review-tab-BPMUyNTx.js.gz +0 -0
  458. package/dist/web/assets/settings-body-Dy5D6Vu7.js.br +0 -0
  459. package/dist/web/assets/settings-body-Dy5D6Vu7.js.gz +0 -0
  460. package/dist/web/assets/settings-rows-BRoYE2To.js.br +0 -0
  461. package/dist/web/assets/settings-rows-BRoYE2To.js.gz +0 -0
  462. package/dist/web/assets/settings-section-content-C4DX7TYv.js +0 -1
  463. package/dist/web/assets/settings-section-content-essUViKu.js.br +0 -0
  464. package/dist/web/assets/settings-section-content-essUViKu.js.gz +0 -0
  465. package/dist/web/assets/slash-command-picker-8da30mYP.js.br +0 -0
  466. package/dist/web/assets/slash-command-picker-8da30mYP.js.gz +0 -0
  467. package/dist/web/assets/sql-object-tab-DRIHVXuR.js.br +0 -0
  468. package/dist/web/assets/sql-object-tab-DRIHVXuR.js.gz +0 -0
  469. package/dist/web/assets/sqlite-viewer-G-Jl2yEr.js.br +0 -0
  470. package/dist/web/assets/sqlite-viewer-G-Jl2yEr.js.gz +0 -0
  471. package/dist/web/assets/structure-save-dialog-Cwikz0we.js.br +0 -0
  472. package/dist/web/assets/structure-save-dialog-Cwikz0we.js.gz +0 -0
  473. package/dist/web/assets/structure-tab-CWEgdZPm.js.br +0 -0
  474. package/dist/web/assets/structure-tab-CWEgdZPm.js.gz +0 -0
  475. package/dist/web/assets/system-monitor-body-BDQm9-wb.js.br +0 -0
  476. package/dist/web/assets/system-monitor-body-BDQm9-wb.js.gz +0 -0
  477. package/dist/web/assets/tab-host-window-content-CZ55-bTz.js.br +0 -0
  478. package/dist/web/assets/tab-host-window-content-CZ55-bTz.js.gz +0 -0
  479. package/dist/web/assets/table-tab-Bmn4UdBV.js.br +0 -0
  480. package/dist/web/assets/table-tab-Bmn4UdBV.js.gz +0 -0
  481. package/dist/web/assets/telegram-bot-token-field-fdHwRf39.js.br +0 -0
  482. package/dist/web/assets/telegram-bot-token-field-fdHwRf39.js.gz +0 -0
  483. package/dist/web/assets/terminal-tab-D-qlZXLK.js.br +0 -0
  484. package/dist/web/assets/terminal-tab-D-qlZXLK.js.gz +0 -0
  485. package/dist/web/assets/tool-cards-BdXbQ9Fv.js.br +0 -0
  486. package/dist/web/assets/tool-cards-BdXbQ9Fv.js.gz +0 -0
  487. package/dist/web/assets/tools-settings-section-CcPaspbR.js.br +0 -0
  488. package/dist/web/assets/tools-settings-section-CcPaspbR.js.gz +0 -0
  489. package/dist/web/assets/use-inline-blame-Ck5XoSS3.js.br +0 -0
  490. package/dist/web/assets/use-lsp-CGSqfFBk.js.br +0 -0
  491. package/dist/web/assets/use-lsp-CGSqfFBk.js.gz +0 -0
  492. package/dist/web/assets/use-process-kill-DJ_f7rCO.js.br +0 -0
  493. package/dist/web/assets/use-process-kill-DJ_f7rCO.js.gz +0 -0
  494. package/dist/web/assets/use-sql-schema-info-wt320fVR.js.br +0 -0
  495. package/dist/web/assets/use-sql-schema-info-wt320fVR.js.gz +0 -0
  496. package/dist/web/assets/web-preview-tab-D4XayUEK.js.br +0 -0
  497. package/dist/web/assets/web-preview-tab-D4XayUEK.js.gz +0 -0
@@ -6,6 +6,12 @@
6
6
  // for the SDK value "xhigh"; the literal "extra" would crash the CLI subprocess,
7
7
  // so it must NEVER appear as a value here.
8
8
 
9
+ import {
10
+ effortEnvMeaning, effortInEffect, effortSettingPlace, isEffortLevel,
11
+ type EffortInEffect, type EffortLevel, type EffortStatus,
12
+ } from "../../../shared/effort-status";
13
+ import { modelDisplayName } from "@/lib/model-display-name";
14
+
9
15
  export interface EffortOption {
10
16
  label: string;
11
17
  value: "low" | "medium" | "high" | "xhigh" | "max";
@@ -31,3 +37,76 @@ export function chipLabel(modelDisplay: string, effortValue: string | null | und
31
37
  const eff = effortLabel(effortValue);
32
38
  return eff ? `${modelDisplay} · ${eff}` : modelDisplay;
33
39
  }
40
+
41
+ export const DEFAULT_EFFORT = EFFORT_OPTIONS.find((o) => o.default)!.value;
42
+
43
+ /** The picked effort beside the one the turns run at, and what overrides the pick if anything does. */
44
+ export function shownEffort(
45
+ effort: string | null | undefined,
46
+ status: EffortStatus | null | undefined,
47
+ ): EffortInEffect & { pick: EffortLevel } {
48
+ const pick = isEffortLevel(effort) ? effort : DEFAULT_EFFORT;
49
+ return { pick, ...effortInEffect(pick, status) };
50
+ }
51
+
52
+ /** Notice text; the names a user would type or search for (a variable, a file) are marked as code. */
53
+ export type NoticePart = string | { code: string };
54
+
55
+ export interface EffortNotice {
56
+ title: string;
57
+ detail: NoticePart[];
58
+ }
59
+
60
+ export function noticeText(parts: NoticePart[]): string {
61
+ return parts.map((part) => (typeof part === "string" ? part : part.code)).join("");
62
+ }
63
+
64
+ const USE_PPM_PICK = "Remove it there to use the effort picked in PPM.";
65
+
66
+ /**
67
+ * Why the effort the turns run at is not the one picked, naming the setting and where it is, so
68
+ * the user can change it. Null when the pick is what runs.
69
+ */
70
+ export function effortNotice(
71
+ shown: EffortInEffect & { pick: EffortLevel },
72
+ status: EffortStatus | null | undefined,
73
+ modelDisplay: string,
74
+ ): EffortNotice | null {
75
+ if (!shown.reason || !status) return null;
76
+ const observedModel = status.observed?.model;
77
+ const model = observedModel ? modelDisplayName(observedModel.replace(/\[[^\]]*\]$/, "")) : modelDisplay;
78
+ const level = shown.level ? effortLabel(shown.level) : null;
79
+
80
+ const meaning = effortEnvMeaning(status.env?.value);
81
+ if (shown.reason === "env" && status.env && meaning) {
82
+ const setting: NoticePart[] = [{ code: `CLAUDE_CODE_EFFORT_LEVEL=${status.env.value}` }, " in ", { code: effortSettingPlace(status.env) }];
83
+ if (meaning.kind === "auto") {
84
+ // With a cap in place the CLI sends the model's default, clamped, rather than nothing, and
85
+ // until it answers that default is not known here, so the sentence names only the cap.
86
+ const what = status.cap
87
+ ? `Claude Code sends ${model}'s default effort, capped at ${effortLabel(status.cap.value)}.`
88
+ : level
89
+ ? `Claude Code sends ${model}'s default, ${level}.`
90
+ : `no effort is sent, so ${model}'s default applies.`;
91
+ return { title: "Effort is left to the model", detail: [...setting, ` overrides the effort picked here: ${what} `, USE_PPM_PICK] };
92
+ }
93
+ if (meaning.kind === "budget") {
94
+ // Under a cap the CLI reads the number as High, clamped, instead of as a thinking budget.
95
+ if (level) {
96
+ return { title: `Effort is set to ${level} outside PPM`, detail: [...setting, ` overrides the effort picked here; with a maxEffortLevel set, Claude Code runs it as ${level}. `, USE_PPM_PICK] };
97
+ }
98
+ return { title: "A thinking budget replaces the effort", detail: [...setting, " sets a thinking budget instead of an effort level, so the effort picked here is not sent. ", USE_PPM_PICK] };
99
+ }
100
+ const runs = shown.level === meaning.level ? "" : level ? ` ${model} runs it as ${level}.` : ` ${model} takes no effort level, so none is sent.`;
101
+ return { title: `Effort is set to ${effortLabel(meaning.level)} outside PPM`, detail: [...setting, ` overrides the effort picked here.${runs} `, USE_PPM_PICK] };
102
+ }
103
+ if (shown.reason === "cap" && status.cap) {
104
+ const runs = shown.level === status.cap.value || !level ? "" : ` ${model} runs it as ${level}.`;
105
+ return {
106
+ title: `Effort is capped at ${effortLabel(status.cap.value)}`,
107
+ detail: [{ code: `maxEffortLevel: "${status.cap.value}"` }, " in ", { code: effortSettingPlace(status.cap) }, ` lowers any effort picked above it.${runs}`],
108
+ };
109
+ }
110
+ if (!level) return { title: `${model} takes no effort level`, detail: [`Claude Code sends no effort with ${model}, so the effort picked here has no effect.`] };
111
+ return { title: `Runs at ${level}`, detail: [`${effortLabel(shown.pick)} isn't available for ${model}, so Claude Code runs it at ${level}.`] };
112
+ }
@@ -1,10 +1,11 @@
1
1
  import { useState, useEffect, useRef, type ReactNode } from "react";
2
- import { Check, Sparkles, Brain } from "@/lib/icons";
2
+ import { Check, Sparkles, Brain, TriangleAlert } from "@/lib/icons";
3
3
  import { api, projectUrl } from "@/lib/api-client";
4
4
  import { useIsMobile } from "@/hooks/use-is-mobile";
5
5
  import { BottomSheet } from "@/components/ui/mobile-bottom-sheet";
6
6
  import { modelDisplayName } from "@/lib/model-display-name";
7
- import { EFFORT_OPTIONS, chipLabel } from "./model-thinking-selector-helpers";
7
+ import type { EffortStatus } from "../../../shared/effort-status";
8
+ import { EFFORT_OPTIONS, chipLabel, effortLabel, effortNotice, noticeText, shownEffort, type EffortNotice } from "./model-thinking-selector-helpers";
8
9
 
9
10
  interface ModelOption {
10
11
  value: string;
@@ -18,6 +19,8 @@ const MODEL_CACHE_TTL = 5 * 60 * 1000;
18
19
  interface ModelThinkingSelectorProps {
19
20
  model: string | null;
20
21
  effort: string | null;
22
+ /** What overrides the effort pick outside PPM, so the chip shows the effort the turns run at. */
23
+ effortStatus?: EffortStatus | null;
21
24
  thinking: boolean;
22
25
  onModelChange: (model: string) => void;
23
26
  onEffortChange: (effort: string) => void;
@@ -33,8 +36,6 @@ function shortLabel(label: string): string {
33
36
  return label.replace(/^Claude\s+/i, "");
34
37
  }
35
38
 
36
- const DEFAULT_EFFORT = EFFORT_OPTIONS.find((o) => o.default)!.value;
37
-
38
39
  /**
39
40
  * Unified model + thinking picker — one chip, one popup. Mirrors Anthropic's native
40
41
  * picker: model list, effort levels (Extra→SDK "xhigh"), and a thinking on/off toggle.
@@ -43,6 +44,7 @@ const DEFAULT_EFFORT = EFFORT_OPTIONS.find((o) => o.default)!.value;
43
44
  export function ModelThinkingSelector({
44
45
  model,
45
46
  effort,
47
+ effortStatus,
46
48
  thinking,
47
49
  onModelChange,
48
50
  onEffortChange,
@@ -109,9 +111,11 @@ export function ModelThinkingSelector({
109
111
 
110
112
  const current = models.find((m) => m.value === model);
111
113
  const modelDisplay = current ? shortLabel(current.label) : model ? modelDisplayName(model) : "Model";
112
- const effortValue = effort ?? DEFAULT_EFFORT;
114
+ const shown = shownEffort(effort, effortStatus);
115
+ const notice = effortNotice(shown, effortStatus, modelDisplay);
113
116
  const showModelList = models.length > 0 || Boolean(modelStatus);
114
- const chipText = chipLabel(modelDisplay, effortValue);
117
+ // The effort the turns run at, which an override outside PPM can make differ from the pick.
118
+ const chipText = chipLabel(modelDisplay, shown.level);
115
119
 
116
120
  const pick = (fn: () => void) => {
117
121
  fn();
@@ -119,6 +123,8 @@ export function ModelThinkingSelector({
119
123
  };
120
124
 
121
125
  const content = (
126
+ <>
127
+ {notice && <EffortNoticeBanner notice={notice} />}
122
128
  <div className="flex divide-x divide-border">
123
129
  {/* Left column: model list */}
124
130
  {showModelList && (
@@ -137,12 +143,13 @@ export function ModelThinkingSelector({
137
143
  <div className="flex-1 min-w-0 py-1">
138
144
  <SectionLabel>Effort</SectionLabel>
139
145
  {EFFORT_OPTIONS.map((o) => (
140
- <OptionRow key={o.value} active={o.value === effortValue} onClick={() => pick(() => onEffortChange(o.value))}>
146
+ <OptionRow key={o.value} active={o.value === shown.pick} onClick={() => pick(() => onEffortChange(o.value))}>
141
147
  <span className="flex-1 truncate">
142
148
  {o.label}
143
149
  {o.default && <span className="ml-2 text-xs text-text-subtle">Default</span>}
150
+ {o.value === shown.level && shown.level !== shown.pick && <span className="ml-2 text-xs text-warning">In effect</span>}
144
151
  </span>
145
- {o.value === effortValue && <Check className="size-4 shrink-0 text-primary" />}
152
+ {o.value === shown.pick && <Check className="size-4 shrink-0 text-primary" />}
146
153
  </OptionRow>
147
154
  ))}
148
155
  <Separator />
@@ -161,6 +168,7 @@ export function ModelThinkingSelector({
161
168
  </button>
162
169
  </div>
163
170
  </div>
171
+ </>
164
172
  );
165
173
 
166
174
  return (
@@ -174,11 +182,12 @@ export function ModelThinkingSelector({
174
182
  if (!disabled) setOpen((v) => !v);
175
183
  }}
176
184
  className="inline-flex max-w-full items-center gap-1.5 px-[9px] py-1 rounded-full text-[11.5px] text-primary bg-accent-wash border border-accent-wash-border hover:brightness-110 transition-[filter] disabled:opacity-50 disabled:cursor-default"
177
- aria-label={`Model ${modelDisplay}, effort ${effortValue}${thinking ? ", thinking on" : ""}`}
178
- title={disabled ? "Can't change while running" : chipText}
185
+ aria-label={`Model ${modelDisplay}, effort ${shown.level ? effortLabel(shown.level) : "none"}${notice ? ` (${notice.title})` : ""}${thinking ? ", thinking on" : ""}`}
186
+ title={disabled ? "Can't change while running" : notice ? `${chipText}\n${notice.title}: ${noticeText(notice.detail)}` : chipText}
179
187
  >
180
188
  <Sparkles className="h-3.5 w-3.5 shrink-0" />
181
189
  <span className="min-w-0 max-w-[140px] truncate">{chipText}</span>
190
+ {notice && <TriangleAlert className="h-3 w-3 shrink-0 text-warning" />}
182
191
  {thinking && <Brain className="h-3 w-3 shrink-0" />}
183
192
  </button>
184
193
 
@@ -204,6 +213,23 @@ export function ModelThinkingSelector({
204
213
  );
205
214
  }
206
215
 
216
+ /** Above both columns, so it reads at full width in the phone's sheet too. */
217
+ function EffortNoticeBanner({ notice }: { notice: EffortNotice }) {
218
+ return (
219
+ <div role="note" className="flex gap-2 border-b border-border bg-warning/10 px-3 py-2.5 text-sm leading-relaxed text-text-secondary md:text-xs">
220
+ <TriangleAlert className="mt-0.5 size-4 shrink-0 text-warning" />
221
+ <div className="min-w-0">
222
+ <div className="font-medium text-text-primary">{notice.title}</div>
223
+ <p className="m-0 break-words">
224
+ {notice.detail.map((part, i) => typeof part === "string"
225
+ ? part
226
+ : <code key={i} className="rounded bg-surface-elevated px-1 font-mono text-[0.92em]">{part.code}</code>)}
227
+ </p>
228
+ </div>
229
+ </div>
230
+ );
231
+ }
232
+
207
233
  function SectionLabel({ children }: { children: ReactNode }) {
208
234
  return <div className="px-3 py-1.5 text-xs font-medium text-text-secondary">{children}</div>;
209
235
  }
@@ -3,8 +3,9 @@
3
3
  * that answer them too: each turn's change tray keeps, reverts and reopens the blocks its edits
4
4
  * wrote, and has to see the same list the bar does — one request for the whole chat.
5
5
  */
6
- import { createContext, useContext } from "react";
6
+ import { createContext, useCallback, useContext, useMemo, useRef } from "react";
7
7
  import type { SessionFileChange } from "../../../shared/session-file-changes";
8
+ import { openSessionReview } from "@/lib/session-file-changes-client";
8
9
 
9
10
  export interface SessionChangesValue {
10
11
  projectName: string;
@@ -19,3 +20,30 @@ export interface SessionChangesValue {
19
20
  export const SessionChangesContext = createContext<SessionChangesValue | null>(null);
20
21
 
21
22
  export const useSessionChanges = () => useContext(SessionChangesContext);
23
+
24
+ /**
25
+ * The value the chat provides, and the Review opener its changes bar uses.
26
+ *
27
+ * Every turn's action bar reads this context, so the value has to stay the same object while
28
+ * the agent streams: `paths` is a new array on every flush, which is why it is read only when
29
+ * the Review tab opens.
30
+ */
31
+ export function useSessionChangesValue({ projectName, sessionId, title, providerId, changes }: {
32
+ projectName: string;
33
+ sessionId: string | null;
34
+ title?: string;
35
+ providerId: string;
36
+ changes: { files: SessionFileChange[]; paths: string[]; refresh: () => void };
37
+ }): { value: SessionChangesValue | null; openReview: (path?: string) => void } {
38
+ const pathsRef = useRef(changes.paths);
39
+ pathsRef.current = changes.paths;
40
+ const openReview = useCallback((path?: string) => {
41
+ if (!sessionId) return;
42
+ openSessionReview({ projectName, sessionId, title, providerId, paths: pathsRef.current, selectPath: path });
43
+ }, [projectName, sessionId, title, providerId]);
44
+ const { files, refresh } = changes;
45
+ const value = useMemo((): SessionChangesValue | null => (
46
+ projectName && sessionId ? { projectName, sessionId, files, refresh, openReview } : null
47
+ ), [projectName, sessionId, files, refresh, openReview]);
48
+ return { value, openReview };
49
+ }
@@ -53,6 +53,32 @@ export function ReparentingTab({
53
53
  const panelIdRef = useRef(panelId);
54
54
  panelIdRef.current = panelId;
55
55
 
56
+ // Where each scroller in the tab last stood, as its own scroll events said. A move resets
57
+ // every offset, and on a project switch the old panel unmounts first, taking the wrapper off
58
+ // the page before the move below runs — too late to read them then. A chat came back at its
59
+ // top, and its history loader took that for the reader scrolling up and fetched another page
60
+ // on every switch back.
61
+ // Held weakly: a scroller React removed would keep its whole tree alive, and a chat replaces
62
+ // its transcript's on every session switch.
63
+ const scrolledRef = useRef(new WeakMap<Element, { top: number; left: number }>());
64
+ /** The keys of `scrolledRef`, which a WeakMap cannot list. */
65
+ const scrollersRef = useRef(new Set<WeakRef<Element>>());
66
+ /** Moved while hidden, when there was no layout to put the offsets back into. */
67
+ const restorePendingRef = useRef(false);
68
+ const restoreScroll = () => {
69
+ for (const ref of scrollersRef.current) {
70
+ const el = ref.deref();
71
+ const at = el && scrolledRef.current.get(el);
72
+ if (!el || !at || !wrapper.contains(el)) {
73
+ scrollersRef.current.delete(ref);
74
+ if (el) scrolledRef.current.delete(el);
75
+ continue;
76
+ }
77
+ el.scrollTop = at.top;
78
+ el.scrollLeft = at.left;
79
+ }
80
+ };
81
+
56
82
  // Mount the wrapper and own its removal. The listener is native and capturing because
57
83
  // the tab content is DOM-reparented out of the panel's fiber subtree: the panel never
58
84
  // sees clicks on the content body, so focus is set here, where the panel is known.
@@ -65,17 +91,39 @@ export function ReparentingTab({
65
91
  if (pid === DOCK_PANEL_ID || isWindowPanelId(pid)) return;
66
92
  usePanelStore.getState().setFocusedPanel(pid);
67
93
  };
94
+ const scrolled = scrolledRef.current;
95
+ const scrollers = scrollersRef.current;
96
+ const onScroll = (e: Event) => {
97
+ // Parked out of sight a scroller has no size, and whatever moves it there is not the reader.
98
+ if (wrapper.parentElement === hiddenContainer) return;
99
+ const el = e.target as Element;
100
+ if (el.nodeType !== Node.ELEMENT_NODE) return;
101
+ if (!scrolled.has(el)) {
102
+ for (const ref of scrollers) if (!ref.deref()) scrollers.delete(ref);
103
+ scrollers.add(new WeakRef(el));
104
+ }
105
+ scrolled.set(el, { top: el.scrollTop, left: el.scrollLeft });
106
+ };
68
107
  wrapper.addEventListener("pointerdown", onMouseDown, true);
69
108
  wrapper.addEventListener("focusin", onMouseDown, true);
109
+ wrapper.addEventListener("scroll", onScroll, true);
70
110
  return () => {
71
111
  wrapper.removeEventListener("pointerdown", onMouseDown, true);
72
112
  wrapper.removeEventListener("focusin", onMouseDown, true);
113
+ wrapper.removeEventListener("scroll", onScroll, true);
73
114
  wrapper.remove();
74
115
  };
75
116
  }, [wrapper, hiddenContainer]);
76
117
 
77
118
  useLayoutEffect(() => {
78
119
  wrapper.style.display = isActive ? "" : "none";
120
+ // Only for a tab moved while hidden: a plain tab switch keeps its offsets, and writing them
121
+ // back forces a layout of the whole tab in the middle of the commit.
122
+ if (isActive && restorePendingRef.current) {
123
+ restorePendingRef.current = false;
124
+ restoreScroll();
125
+ }
126
+ // eslint-disable-next-line react-hooks/exhaustive-deps
79
127
  }, [wrapper, isActive]);
80
128
 
81
129
  // Imperatively move the wrapper into the correct panel slot.
@@ -96,21 +144,12 @@ export function ReparentingTab({
96
144
 
97
145
  if (wrapper.parentElement === slot) return;
98
146
 
99
- // Save scroll positions — appendChild resets them during the DOM move
100
- const scrollables: { el: Element; top: number; left: number }[] = [];
101
- wrapper.querySelectorAll("*").forEach((el) => {
102
- if (el.scrollTop || el.scrollLeft) {
103
- scrollables.push({ el, top: el.scrollTop, left: el.scrollLeft });
104
- }
105
- });
106
-
107
147
  slot.appendChild(wrapper);
108
-
109
- // Restore scroll positions synchronously before paint
110
- for (const { el, top, left } of scrollables) {
111
- el.scrollTop = top;
112
- el.scrollLeft = left;
113
- }
148
+ // Before paint, from what the scroll events recorded: walking every element of the tab
149
+ // to read offsets cost a whole transcript's worth of reads per move, and read zeros
150
+ // whenever the wrapper had already left the page.
151
+ restorePendingRef.current = !isActive;
152
+ if (isActive) restoreScroll();
114
153
  });
115
154
 
116
155
  // Portal target for the tab's own dropdowns, tooltips and sheets. It belongs here and
@@ -17,6 +17,9 @@ import { ProviderBadge } from "@/components/chat/provider-selector";
17
17
  import { openSettings } from "./open-settings";
18
18
  import { CodexContextSettings } from "./codex-context-settings";
19
19
  import { McpSignInList } from "@/components/mcp-auth/mcp-sign-in-list";
20
+ import { effortNotice, noticeText, shownEffort } from "@/components/chat/model-thinking-selector-helpers";
21
+ import { modelDisplayName } from "@/lib/model-display-name";
22
+ import type { EffortStatus } from "../../../shared/effort-status";
20
23
  import type { ModelOption } from "../../../types/chat";
21
24
  import type { ProviderProbeStatus } from "../../../providers/provider-probe";
22
25
 
@@ -88,6 +91,21 @@ export function AISettingsSection({ compact }: { compact?: boolean } = {}) {
88
91
  .finally(() => setModelsLoading(false));
89
92
  }, [activeTab, activeRegistered]);
90
93
 
94
+ // `CLAUDE_CODE_EFFORT_LEVEL` or a cap in the user's Claude Code settings makes the default
95
+ // below inert for every project, which the select alone would not show.
96
+ const [effortStatus, setEffortStatus] = useState<EffortStatus | null>(null);
97
+ const defaultModel = settings?.providers[activeTab]?.model;
98
+ useEffect(() => {
99
+ setEffortStatus(null);
100
+ if (!activeTab) return;
101
+ let active = true;
102
+ const query = defaultModel ? `?model=${encodeURIComponent(defaultModel)}` : "";
103
+ api.get<{ status: EffortStatus | null }>(`/api/settings/ai/providers/${activeTab}/effort-status${query}`)
104
+ .then(({ status }) => { if (active) setEffortStatus(status); })
105
+ .catch(() => {});
106
+ return () => { active = false; };
107
+ }, [activeTab, defaultModel]);
108
+
91
109
  const retryProbe = async () => {
92
110
  setProbing(true);
93
111
  try {
@@ -108,6 +126,11 @@ export function AISettingsSection({ compact }: { compact?: boolean } = {}) {
108
126
 
109
127
  const config = settings?.providers[activeTab];
110
128
  const isSdkProvider = config?.type === "agent-sdk" || (!config?.type && activeTab === "claude");
129
+ const effortOverride = effortNotice(
130
+ shownEffort(config?.effort, effortStatus),
131
+ effortStatus,
132
+ config?.model ? modelDisplayName(config.model) : "the model",
133
+ );
111
134
 
112
135
  const handleSettingsSave = async (patch: Partial<AISettings>) => {
113
136
  if (!settings) return false;
@@ -359,6 +382,15 @@ export function AISettingsSection({ compact }: { compact?: boolean } = {}) {
359
382
  ))}
360
383
  </SelectContent>
361
384
  </Select>
385
+ {effortOverride && (
386
+ <div role="note" className="flex items-start gap-2 rounded-md border border-warning/30 bg-warning/5 px-2.5 py-2">
387
+ <AlertTriangle className="size-3.5 text-warning shrink-0 mt-0.5" />
388
+ <div className="min-w-0 flex-1 space-y-1">
389
+ <p className={`${labelSize} text-text-secondary`}>{effortOverride.title}</p>
390
+ <p className="text-[11px] text-muted-foreground break-words">{noticeText(effortOverride.detail)}</p>
391
+ </div>
392
+ </div>
393
+ )}
362
394
  </div>
363
395
 
364
396
  <div className={fieldGap}>
@@ -144,6 +144,8 @@ function PushSection() {
144
144
  const [subscription, setSubscription] = useState<PushSubscription | null>(null);
145
145
  const [loadError, setLoadError] = useState<string | null>(null);
146
146
  const [busy, setBusy] = useState(false);
147
+ const [enabling, setEnabling] = useState(false);
148
+ const [enableError, setEnableError] = useState<string | null>(null);
147
149
  const [testing, setTesting] = useState<string | null>(null);
148
150
  const support = pushSupport();
149
151
 
@@ -174,6 +176,8 @@ function PushSection() {
174
176
  const toggle = async (on: boolean) => {
175
177
  if (!status) return;
176
178
  setBusy(true);
179
+ setEnabling(on);
180
+ setEnableError(null);
177
181
  try {
178
182
  if (on) {
179
183
  await enablePush(status.publicKey);
@@ -182,9 +186,12 @@ function PushSection() {
182
186
  await disablePush();
183
187
  }
184
188
  } catch (e) {
185
- toast.error(on ? "Could not turn push on" : "Could not turn push off", { description: errorText(e) });
189
+ // Kept on screen rather than in a toast: the fix is usually a browser setting to go and change.
190
+ if (on) setEnableError(errorText(e));
191
+ else toast.error("Could not turn push off", { description: errorText(e) });
186
192
  } finally {
187
193
  setBusy(false);
194
+ setEnabling(false);
188
195
  await load();
189
196
  }
190
197
  };
@@ -231,6 +238,12 @@ function PushSection() {
231
238
  />
232
239
  </div>
233
240
  {support.state !== "available" && <p className="text-xs leading-relaxed text-warning">{support.message}</p>}
241
+ {enabling && (
242
+ <p className="text-xs leading-relaxed text-muted-foreground">
243
+ Waiting for the browser's push service. Just after the browser starts, this can take up to a minute.
244
+ </p>
245
+ )}
246
+ {enableError && <p className="break-words text-xs leading-relaxed text-error">{enableError}</p>}
234
247
  {loadError && <p className="text-xs text-error">{loadError}</p>}
235
248
 
236
249
  {status && status.devices.length > 0 && (
@@ -13,6 +13,7 @@ import type { ChatMessage, ChatEvent } from "../../types/chat";
13
13
  import type { BackgroundAgentStatus } from "../../shared/background-agent-status";
14
14
  import type { PromptCacheState } from "../../shared/prompt-cache-idle";
15
15
  import type { TurnStop } from "../../shared/turn-stop";
16
+ import type { EffortStatus } from "../../shared/effort-status";
16
17
  import { decodeReply, encodeReply, type ReplyReference } from "../../shared/chat-reply";
17
18
  import { prefixTokens } from "../../shared/turn-usage";
18
19
  import { dbExecuteApprovalInput } from "../../shared/db-ai-tools";
@@ -131,6 +132,11 @@ interface UseChatReturn {
131
132
  effort: string | null;
132
133
  /** Switch the per-session effort (low|medium|high|xhigh|max) */
133
134
  setEffort: (effort: string) => void;
135
+ /**
136
+ * What overrides the effort pick outside PPM, as the server last said: undefined until it
137
+ * has, null when nothing can for this session's provider.
138
+ */
139
+ effortStatus: EffortStatus | null | undefined;
134
140
  /** Whether per-session thinking is on */
135
141
  thinking: boolean;
136
142
  /** Toggle per-session thinking on/off */
@@ -251,6 +257,7 @@ export function useChat(
251
257
  const pendingModelRef = useRef<string | null>(null);
252
258
  const [effort, setEffortState] = useState<string | null>(null);
253
259
  const effortRef = useRef<string | null>(null);
260
+ const [effortStatus, setEffortStatus] = useState<EffortStatus | null | undefined>(undefined);
254
261
  // Optimistic until session_state lands; the SDK default is adaptive thinking, so ON
255
262
  // is the honest starting guess.
256
263
  const [thinking, setThinkingState] = useState<boolean>(true);
@@ -1021,6 +1028,11 @@ export function useChat(
1021
1028
  return;
1022
1029
  }
1023
1030
 
1031
+ if ((data as any).type === "effort_status") {
1032
+ setEffortStatus(((data as any).status as EffortStatus | undefined) ?? null);
1033
+ return;
1034
+ }
1035
+
1024
1036
  // Handle compact status events
1025
1037
  if ((data as any).type === "compact_status") {
1026
1038
  const status = (data as any).status;
@@ -1096,6 +1108,8 @@ export function useChat(
1096
1108
  setEffortState(state.effort);
1097
1109
  effortRef.current = state.effort;
1098
1110
  }
1111
+ // Only the greetings that carry the effort carry this; the others leave it as it was.
1112
+ if ("effortStatus" in state) setEffortStatus((state.effortStatus as EffortStatus | null) ?? null);
1099
1113
  // Display only. Writing this back into the ref would promote a server-derived
1100
1114
  // default into an explicit user choice that rides along on every message and
1101
1115
  // pins the session — the path that silently disabled thinking.
@@ -1289,6 +1303,7 @@ export function useChat(
1289
1303
  // Another session's subprocess saw another MCP state; its session_state brings its own.
1290
1304
  setMcpNeedsAuth([]);
1291
1305
  setTurnStop(null);
1306
+ setEffortStatus(undefined);
1292
1307
  // Clear ephemeral pre-compact expansions on session change
1293
1308
  setExpansions(new Map());
1294
1309
  // Drop the previous session's version groups. Keeping them would let the
@@ -1819,6 +1834,7 @@ export function useChat(
1819
1834
  setModel,
1820
1835
  effort,
1821
1836
  setEffort,
1837
+ effortStatus,
1822
1838
  thinking,
1823
1839
  setThinking,
1824
1840
  turnSettings,
@@ -0,0 +1,46 @@
1
+ import { useEffect, useState } from "react";
2
+ import { api, projectUrl } from "@/lib/api-client";
3
+ import type { EffortStatus } from "../../shared/effort-status";
4
+
5
+ /** Short-lived: it is read from settings files the user may be editing right now. */
6
+ const CACHE_MS = 30_000;
7
+ const cache = new Map<string, { status: EffortStatus | null; at: number }>();
8
+
9
+ /**
10
+ * What overrides the effort pick outside PPM. A session's own status comes over its socket once
11
+ * the server has sent it; a chat with no session yet — the new-chat composer — asks what the
12
+ * project's settings files say, so the chip is right before the first message too.
13
+ */
14
+ export function useEffortStatus(p: {
15
+ projectName?: string;
16
+ providerId?: string;
17
+ model?: string | null;
18
+ sessionStatus: EffortStatus | null | undefined;
19
+ }): EffortStatus | null {
20
+ const { projectName, providerId, model, sessionStatus } = p;
21
+ const key = JSON.stringify([projectName ?? null, providerId ?? null, model ?? null]);
22
+ const wanted = sessionStatus === undefined && !!projectName && !!providerId;
23
+ const [fetched, setFetched] = useState<{ key: string; status: EffortStatus | null } | null>(null);
24
+
25
+ useEffect(() => {
26
+ if (!wanted) return;
27
+ const hit = cache.get(key);
28
+ if (hit && Date.now() - hit.at < CACHE_MS) {
29
+ setFetched({ key, status: hit.status });
30
+ return;
31
+ }
32
+ let active = true;
33
+ const query = model ? `?model=${encodeURIComponent(model)}` : "";
34
+ api.get<{ status: EffortStatus | null }>(`${projectUrl(projectName!)}/chat/providers/${providerId}/effort-status${query}`)
35
+ .then(({ status }) => {
36
+ cache.set(key, { status, at: Date.now() });
37
+ if (active) setFetched({ key, status });
38
+ })
39
+ // Nothing to say then; the chip shows the pick as it always did.
40
+ .catch(() => {});
41
+ return () => { active = false; };
42
+ }, [wanted, key, projectName, providerId, model]);
43
+
44
+ if (sessionStatus !== undefined) return sessionStatus;
45
+ return fetched?.key === key ? fetched.status : cache.get(key)?.status ?? null;
46
+ }
@@ -0,0 +1,35 @@
1
+ import { useSyncExternalStore } from "react";
2
+
3
+ /**
4
+ * Re-renders the caller every half minute, for labels such as "5m ago".
5
+ *
6
+ * The chat's labels used to stay current only because the whole transcript was redrawn on
7
+ * every render of the chat; now that it is not, they ask for the time themselves. One timer for
8
+ * the page, running only while a label is mounted.
9
+ */
10
+ export const RELATIVE_TIME_TICK_MS = 30_000;
11
+
12
+ let tick = 0;
13
+ let timer: ReturnType<typeof setInterval> | undefined;
14
+ const listeners = new Set<() => void>();
15
+
16
+ function subscribe(listener: () => void): () => void {
17
+ listeners.add(listener);
18
+ timer ??= setInterval(() => {
19
+ tick++;
20
+ for (const notify of listeners) notify();
21
+ }, RELATIVE_TIME_TICK_MS);
22
+ return () => {
23
+ listeners.delete(listener);
24
+ if (listeners.size === 0 && timer !== undefined) {
25
+ clearInterval(timer);
26
+ timer = undefined;
27
+ }
28
+ };
29
+ }
30
+
31
+ const snapshot = () => tick;
32
+
33
+ export function useRelativeTimeTick(): number {
34
+ return useSyncExternalStore(subscribe, snapshot, snapshot);
35
+ }
@@ -35,6 +35,10 @@ import {
35
35
  import type { ChatMessage } from "../../types/chat";
36
36
  import { SESSION_FILE_CHANGES_PUSH, type SessionFileChange, type SessionFileChangesPushEvent } from "../../shared/session-file-changes";
37
37
 
38
+ // One array for "not asked yet", which is most chats: a fresh `[]` per render reached every
39
+ // turn's action bar through the context and redrew them all on each render of the chat.
40
+ const NO_FILES: SessionFileChange[] = [];
41
+
38
42
  export function useSessionFileChanges({ projectName, sessionId, messages, isStreaming }: {
39
43
  projectName: string;
40
44
  sessionId: string | null;
@@ -173,5 +177,5 @@ export function useSessionFileChanges({ projectName, sessionId, messages, isStre
173
177
 
174
178
  // A list fetched for the previous session is never shown under this one.
175
179
  const unloadedCount = Math.max(paths.length, count.key === key ? count.n : 0);
176
- return { files: loaded ? state.files : [], paths, unloadedCount, loaded, loading: loadingKey === key && key !== "", refresh, setReviewed };
180
+ return { files: loaded ? state.files : NO_FILES, paths, unloadedCount, loaded, loading: loadingKey === key && key !== "", refresh, setReviewed };
177
181
  }