@hienlh/ppm 0.22.8 → 0.22.10

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 (315) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/CLAUDE.md +3 -0
  3. package/assets/skills/ppm/SKILL.md +1 -1
  4. package/assets/skills/ppm/references/http-api.md +11 -1
  5. package/bun.lock +10 -10
  6. package/dist/web/assets/accounts-settings-section-BYz8j7Fq.js +1 -0
  7. package/dist/web/assets/accounts-settings-section-BYz8j7Fq.js.br +0 -0
  8. package/dist/web/assets/accounts-settings-section-BYz8j7Fq.js.gz +0 -0
  9. package/dist/web/assets/{ai-resource-editor-CtvWAxMW.js → ai-resource-editor-K7Q5d1jo.js} +1 -1
  10. package/dist/web/assets/ai-resource-editor-K7Q5d1jo.js.br +0 -0
  11. package/dist/web/assets/ai-resource-editor-K7Q5d1jo.js.gz +0 -0
  12. package/dist/web/assets/ai-settings-section-ChEBg9Be.js +1 -0
  13. package/dist/web/assets/ai-settings-section-CyWSTidP.js +1 -0
  14. package/dist/web/assets/ai-settings-section-CyWSTidP.js.br +0 -0
  15. package/dist/web/assets/ai-settings-section-CyWSTidP.js.gz +0 -0
  16. package/dist/web/assets/{appearance-settings-section-DRWpbQqj.js → appearance-settings-section-DH0DxaKv.js} +1 -1
  17. package/dist/web/assets/appearance-settings-section-DH0DxaKv.js.br +0 -0
  18. package/dist/web/assets/appearance-settings-section-DH0DxaKv.js.gz +0 -0
  19. package/dist/web/assets/{branch-review-tab-B3BMVCdK.js → branch-review-tab-BjPYFJ7n.js} +1 -1
  20. package/dist/web/assets/branch-review-tab-BjPYFJ7n.js.br +0 -0
  21. package/dist/web/assets/branch-review-tab-BjPYFJ7n.js.gz +0 -0
  22. package/dist/web/assets/chat-tab-BFrvTu8k.js +15 -0
  23. package/dist/web/assets/chat-tab-BFrvTu8k.js.br +0 -0
  24. package/dist/web/assets/chat-tab-BFrvTu8k.js.gz +0 -0
  25. package/dist/web/assets/code-editor-CTTLNDyE.js +7 -0
  26. package/dist/web/assets/code-editor-CTTLNDyE.js.br +0 -0
  27. package/dist/web/assets/code-editor-CTTLNDyE.js.gz +0 -0
  28. package/dist/web/assets/{codex-daily-guard-DjLeZ_hE.js → codex-daily-guard-PsB0g8B0.js} +1 -1
  29. package/dist/web/assets/codex-daily-guard-PsB0g8B0.js.br +0 -0
  30. package/dist/web/assets/codex-daily-guard-PsB0g8B0.js.gz +0 -0
  31. package/dist/web/assets/{conflict-editor-Dx8MnO6j.js → conflict-editor-BoLsr5fL.js} +1 -1
  32. package/dist/web/assets/conflict-editor-BoLsr5fL.js.br +0 -0
  33. package/dist/web/assets/conflict-editor-BoLsr5fL.js.gz +0 -0
  34. package/dist/web/assets/{csv-preview-U-Fy9KPT.js → csv-preview-C-7PUd6W.js} +1 -1
  35. package/dist/web/assets/csv-preview-C-7PUd6W.js.br +0 -0
  36. package/dist/web/assets/csv-preview-C-7PUd6W.js.gz +0 -0
  37. package/dist/web/assets/database-viewer-BJgZ-0s2.js +1 -0
  38. package/dist/web/assets/database-viewer-BJgZ-0s2.js.br +0 -0
  39. package/dist/web/assets/database-viewer-BJgZ-0s2.js.gz +0 -0
  40. package/dist/web/assets/{diff-viewer-Bv2J64t8.js → diff-viewer-i0_wCJL2.js} +1 -1
  41. package/dist/web/assets/diff-viewer-i0_wCJL2.js.br +0 -0
  42. package/dist/web/assets/diff-viewer-i0_wCJL2.js.gz +0 -0
  43. package/dist/web/assets/{editor-language-service-C1J6iorq.js → editor-language-service-DDhZjZqB.js} +1 -1
  44. package/dist/web/assets/editor-language-service-DDhZjZqB.js.br +0 -0
  45. package/dist/web/assets/editor-language-service-DDhZjZqB.js.gz +0 -0
  46. package/dist/web/assets/explorer-body-DeIIj3uF.js +2 -0
  47. package/dist/web/assets/explorer-body-DeIIj3uF.js.br +0 -0
  48. package/dist/web/assets/explorer-body-DeIIj3uF.js.gz +0 -0
  49. package/dist/web/assets/{explorer-window-content-CZIwX6cE.js → explorer-window-content-D0i22zgM.js} +1 -1
  50. package/dist/web/assets/explorer-window-content-D0i22zgM.js.br +0 -0
  51. package/dist/web/assets/explorer-window-content-D0i22zgM.js.gz +0 -0
  52. package/dist/web/assets/{extension-manager-section-DGSKz5iJ.js → extension-manager-section-D8TSwRG8.js} +1 -1
  53. package/dist/web/assets/extension-manager-section-D8TSwRG8.js.br +0 -0
  54. package/dist/web/assets/extension-manager-section-D8TSwRG8.js.gz +0 -0
  55. package/dist/web/assets/{extension-webview-FuuXP0Aw.js → extension-webview-CfzJpLQR.js} +1 -1
  56. package/dist/web/assets/extension-webview-CfzJpLQR.js.br +0 -0
  57. package/dist/web/assets/extension-webview-CfzJpLQR.js.gz +0 -0
  58. package/dist/web/assets/{files-settings-section-CGd2yfzl.js → files-settings-section-Ne10pnQU.js} +1 -1
  59. package/dist/web/assets/files-settings-section-Ne10pnQU.js.br +0 -0
  60. package/dist/web/assets/files-settings-section-Ne10pnQU.js.gz +0 -0
  61. package/dist/web/assets/{general-settings-section-CcG10nJq.js → general-settings-section-fbSqpiKY.js} +1 -1
  62. package/dist/web/assets/general-settings-section-fbSqpiKY.js.br +0 -0
  63. package/dist/web/assets/general-settings-section-fbSqpiKY.js.gz +0 -0
  64. package/dist/web/assets/{git-log-panel-B-aSlSlu.js → git-log-panel-9mRsulRQ.js} +1 -1
  65. package/dist/web/assets/git-log-panel-9mRsulRQ.js.br +0 -0
  66. package/dist/web/assets/git-log-panel-9mRsulRQ.js.gz +0 -0
  67. package/dist/web/assets/{glide-data-grid-usB_4gTg.js → glide-data-grid-OhxZFlYg.js} +3 -3
  68. package/dist/web/assets/glide-data-grid-OhxZFlYg.js.br +0 -0
  69. package/dist/web/assets/glide-data-grid-OhxZFlYg.js.gz +0 -0
  70. package/dist/web/assets/group-chat-tab-4DdMvOkG.js +1 -0
  71. package/dist/web/assets/group-chat-tab-4DdMvOkG.js.br +0 -0
  72. package/dist/web/assets/group-chat-tab-4DdMvOkG.js.gz +0 -0
  73. package/dist/web/assets/index-C3v3QRVR.js +52 -0
  74. package/dist/web/assets/index-C3v3QRVR.js.br +0 -0
  75. package/dist/web/assets/index-C3v3QRVR.js.gz +0 -0
  76. package/dist/web/assets/index-aXJy_SvC.css +1 -0
  77. package/dist/web/assets/index-aXJy_SvC.css.br +0 -0
  78. package/dist/web/assets/index-aXJy_SvC.css.gz +0 -0
  79. package/dist/web/assets/{jira-watcher-section-cDMBzIR7.js → jira-watcher-section-PYPh3xGU.js} +1 -1
  80. package/dist/web/assets/jira-watcher-section-PYPh3xGU.js.br +0 -0
  81. package/dist/web/assets/jira-watcher-section-PYPh3xGU.js.gz +0 -0
  82. package/dist/web/assets/keybindings-store-DhAtn4Pd.js +1 -0
  83. package/dist/web/assets/{keyboard-shortcuts-section-CIcdYToP.js → keyboard-shortcuts-section-CM3pNiBR.js} +1 -1
  84. package/dist/web/assets/keyboard-shortcuts-section-CM3pNiBR.js.br +0 -0
  85. package/dist/web/assets/keyboard-shortcuts-section-CM3pNiBR.js.gz +0 -0
  86. package/dist/web/assets/{markdown-renderer-BzX5WysW.js → markdown-renderer-Df1vynCr.js} +1 -1
  87. package/dist/web/assets/{markdown-renderer-KTNg80_A.js → markdown-renderer-DqNZUt1S.js} +3 -3
  88. package/dist/web/assets/markdown-renderer-DqNZUt1S.js.br +0 -0
  89. package/dist/web/assets/{markdown-renderer-KTNg80_A.js.gz → markdown-renderer-DqNZUt1S.js.gz} +0 -0
  90. package/dist/web/assets/{mobile-explorer-sheet-cnwXe-Uz.js → mobile-explorer-sheet-BSuzC_08.js} +1 -1
  91. package/dist/web/assets/mobile-explorer-sheet-BSuzC_08.js.br +0 -0
  92. package/dist/web/assets/mobile-explorer-sheet-BSuzC_08.js.gz +0 -0
  93. package/dist/web/assets/notification-store-DbKwLWwl.js +1 -0
  94. package/dist/web/assets/{ppmbot-settings-section-CDp69sHV.js → ppmbot-settings-section-CY_yAGFM.js} +1 -1
  95. package/dist/web/assets/ppmbot-settings-section-CY_yAGFM.js.br +0 -0
  96. package/dist/web/assets/ppmbot-settings-section-CY_yAGFM.js.gz +0 -0
  97. package/dist/web/assets/{problems-panel-BiE3c-31.js → problems-panel-CJNasbnt.js} +1 -1
  98. package/dist/web/assets/problems-panel-CJNasbnt.js.br +0 -0
  99. package/dist/web/assets/problems-panel-CJNasbnt.js.gz +0 -0
  100. package/dist/web/assets/{process-table-DcEq9HCl.js → process-table-1CN3rBVp.js} +1 -1
  101. package/dist/web/assets/process-table-1CN3rBVp.js.br +0 -0
  102. package/dist/web/assets/process-table-1CN3rBVp.js.gz +0 -0
  103. package/dist/web/assets/{proxy-settings-section-P_ULFRpZ.js → proxy-settings-section-CqcvneFB.js} +1 -1
  104. package/dist/web/assets/proxy-settings-section-CqcvneFB.js.br +0 -0
  105. package/dist/web/assets/proxy-settings-section-CqcvneFB.js.gz +0 -0
  106. package/dist/web/assets/{remote-desktop-clipboard-notice-COjdmADR.js → remote-desktop-clipboard-notice-BysFGa_4.js} +1 -1
  107. package/dist/web/assets/remote-desktop-clipboard-notice-BysFGa_4.js.br +0 -0
  108. package/dist/web/assets/remote-desktop-clipboard-notice-BysFGa_4.js.gz +0 -0
  109. package/dist/web/assets/{remote-desktop-mobile-sheet-lCU5l2w4.js → remote-desktop-mobile-sheet-DLW_0HDz.js} +2 -2
  110. package/dist/web/assets/remote-desktop-mobile-sheet-DLW_0HDz.js.br +0 -0
  111. package/dist/web/assets/remote-desktop-mobile-sheet-DLW_0HDz.js.gz +0 -0
  112. package/dist/web/assets/{remote-desktop-mobile-view-Bo-wgZPC.js → remote-desktop-mobile-view-Df6SfRtI.js} +1 -1
  113. package/dist/web/assets/remote-desktop-mobile-view-Df6SfRtI.js.br +0 -0
  114. package/dist/web/assets/remote-desktop-mobile-view-Df6SfRtI.js.gz +0 -0
  115. package/dist/web/assets/{remote-desktop-readiness-gate-Cqz8kqWG.js → remote-desktop-readiness-gate-LugXhLJ3.js} +1 -1
  116. package/dist/web/assets/remote-desktop-readiness-gate-LugXhLJ3.js.br +0 -0
  117. package/dist/web/assets/remote-desktop-readiness-gate-LugXhLJ3.js.gz +0 -0
  118. package/dist/web/assets/remote-desktop-window-content-k-pIAIDq.js +1 -0
  119. package/dist/web/assets/remote-desktop-window-content-k-pIAIDq.js.br +0 -0
  120. package/dist/web/assets/remote-desktop-window-content-k-pIAIDq.js.gz +0 -0
  121. package/dist/web/assets/{schedules-settings-section-DHs84DvG.js → schedules-settings-section-ByCm7IOg.js} +1 -1
  122. package/dist/web/assets/schedules-settings-section-ByCm7IOg.js.br +0 -0
  123. package/dist/web/assets/schedules-settings-section-ByCm7IOg.js.gz +0 -0
  124. package/dist/web/assets/{settings-body-CLt6iutX.js → settings-body-Dwt_rUSa.js} +2 -2
  125. package/dist/web/assets/settings-body-Dwt_rUSa.js.br +0 -0
  126. package/dist/web/assets/settings-body-Dwt_rUSa.js.gz +0 -0
  127. package/dist/web/assets/settings-store-m6qOXoy4.js +2 -0
  128. package/dist/web/assets/settings-store-m6qOXoy4.js.br +0 -0
  129. package/dist/web/assets/settings-store-m6qOXoy4.js.gz +0 -0
  130. package/dist/web/assets/{settings-tab-DjOxh-vW.js → settings-tab-Dvj9UfzL.js} +1 -1
  131. package/dist/web/assets/{settings-window-content-BxzSerga.js → settings-window-content-Di-eONmj.js} +1 -1
  132. package/dist/web/assets/{sql-query-editor-4Okd-7ss.js → sql-query-editor-DgwwJxwJ.js} +1 -1
  133. package/dist/web/assets/sql-query-editor-DgwwJxwJ.js.br +0 -0
  134. package/dist/web/assets/sql-query-editor-DgwwJxwJ.js.gz +0 -0
  135. package/dist/web/assets/{sqlite-viewer-BqDUckIJ.js → sqlite-viewer-gxiJZ3kk.js} +1 -1
  136. package/dist/web/assets/sqlite-viewer-gxiJZ3kk.js.br +0 -0
  137. package/dist/web/assets/sqlite-viewer-gxiJZ3kk.js.gz +0 -0
  138. package/dist/web/assets/{system-monitor-body-Ssmt-r5H.js → system-monitor-body-BJrVntEe.js} +2 -2
  139. package/dist/web/assets/system-monitor-body-BJrVntEe.js.br +0 -0
  140. package/dist/web/assets/system-monitor-body-BJrVntEe.js.gz +0 -0
  141. package/dist/web/assets/{system-monitor-tab-DNe62hWL.js → system-monitor-tab-Uz9A5I07.js} +1 -1
  142. package/dist/web/assets/{system-monitor-window-content-C6vnW_pQ.js → system-monitor-window-content-9cKlCVN0.js} +1 -1
  143. package/dist/web/assets/{tab-host-window-content--BVyPR1f.js → tab-host-window-content-D3M-lRyv.js} +1 -1
  144. package/dist/web/assets/tab-host-window-content-D3M-lRyv.js.br +0 -0
  145. package/dist/web/assets/tab-host-window-content-D3M-lRyv.js.gz +0 -0
  146. package/dist/web/assets/{team-member-sheet-CE2eHuFX.js → team-member-sheet-D66xVR6G.js} +2 -2
  147. package/dist/web/assets/team-member-sheet-D66xVR6G.js.br +0 -0
  148. package/dist/web/assets/team-member-sheet-D66xVR6G.js.gz +0 -0
  149. package/dist/web/assets/{team-member-window-content-B1sg0R4H.js → team-member-window-content-D6SxpWVr.js} +1 -1
  150. package/dist/web/assets/team-member-window-content-D6SxpWVr.js.br +0 -0
  151. package/dist/web/assets/team-member-window-content-D6SxpWVr.js.gz +0 -0
  152. package/dist/web/assets/{terminal-tab-BrdS4bAy.js → terminal-tab-DUfOyHxp.js} +3 -3
  153. package/dist/web/assets/terminal-tab-DUfOyHxp.js.br +0 -0
  154. package/dist/web/assets/terminal-tab-DUfOyHxp.js.gz +0 -0
  155. package/dist/web/assets/{tool-cards-DzYJVhjl.js → tool-cards-O9N-oJuB.js} +3 -3
  156. package/dist/web/assets/tool-cards-O9N-oJuB.js.br +0 -0
  157. package/dist/web/assets/tool-cards-O9N-oJuB.js.gz +0 -0
  158. package/dist/web/assets/{use-inline-blame-CKu-Sd-l.js → use-inline-blame-B40AsaN2.js} +1 -1
  159. package/dist/web/assets/use-inline-blame-B40AsaN2.js.br +0 -0
  160. package/dist/web/assets/use-inline-blame-B40AsaN2.js.gz +0 -0
  161. package/dist/web/assets/{use-lsp-BaG0NUXY.js → use-lsp-B30TcKvt.js} +1 -1
  162. package/dist/web/assets/use-lsp-B30TcKvt.js.br +0 -0
  163. package/dist/web/assets/use-lsp-B30TcKvt.js.gz +0 -0
  164. package/dist/web/assets/{use-monaco-theme-DvWcXiCH.js → use-monaco-theme-DGi9_j5M.js} +1 -1
  165. package/dist/web/assets/{use-websocket-Dj0e6N6t.js → use-websocket-DdGdvJc5.js} +1 -1
  166. package/dist/web/assets/video-preview-RS5jKlrC.js +1 -0
  167. package/dist/web/assets/video-preview-RS5jKlrC.js.br +0 -0
  168. package/dist/web/assets/video-preview-RS5jKlrC.js.gz +0 -0
  169. package/dist/web/index.html +3 -3
  170. package/dist/web/index.html.br +0 -0
  171. package/dist/web/index.html.gz +0 -0
  172. package/dist/web/sw.js +1 -1
  173. package/dist/web/sw.js.br +0 -0
  174. package/dist/web/sw.js.gz +0 -0
  175. package/docs/codebase-summary.md +1 -1
  176. package/docs/project-roadmap.md +1 -1
  177. package/package.json +2 -2
  178. package/src/providers/claude-agent-sdk.ts +87 -8
  179. package/src/server/index.ts +9 -0
  180. package/src/server/routes/mcp-auth.ts +117 -0
  181. package/src/server/routes/settings.ts +2 -0
  182. package/src/server/ws/chat-mcp-sign-in-sync.ts +58 -0
  183. package/src/server/ws/chat.ts +35 -1
  184. package/src/services/mcp-oauth/mcp-control-query.ts +58 -0
  185. package/src/services/mcp-oauth/mcp-oauth-flows.ts +271 -0
  186. package/src/services/mcp-oauth/mcp-oauth-redirect.ts +68 -0
  187. package/src/services/mcp-oauth/mcp-status-probe.ts +76 -0
  188. package/src/web/components/ai-resources/ai-resources-panel.tsx +4 -0
  189. package/src/web/components/chat/chat-tab.tsx +5 -0
  190. package/src/web/components/mcp-auth/claude-ai-connector.ts +11 -0
  191. package/src/web/components/mcp-auth/mcp-server-name.ts +10 -0
  192. package/src/web/components/mcp-auth/mcp-sign-in-bar.tsx +74 -0
  193. package/src/web/components/mcp-auth/mcp-sign-in-dialog.tsx +196 -0
  194. package/src/web/components/mcp-auth/mcp-sign-in-list.tsx +110 -0
  195. package/src/web/components/mcp-auth/use-mcp-sign-in-flow.ts +120 -0
  196. package/src/web/components/settings/ai-settings-section.tsx +11 -0
  197. package/src/web/hooks/use-chat.ts +14 -0
  198. package/src/web/lib/api-mcp-auth.ts +54 -0
  199. package/src/web/stores/settings-store.ts +34 -0
  200. package/dist/web/assets/accounts-settings-section-Dk9qWWTy.js +0 -1
  201. package/dist/web/assets/accounts-settings-section-Dk9qWWTy.js.br +0 -0
  202. package/dist/web/assets/accounts-settings-section-Dk9qWWTy.js.gz +0 -0
  203. package/dist/web/assets/ai-resource-editor-CtvWAxMW.js.br +0 -0
  204. package/dist/web/assets/ai-resource-editor-CtvWAxMW.js.gz +0 -0
  205. package/dist/web/assets/ai-settings-section-BxN-zFfm.js +0 -1
  206. package/dist/web/assets/ai-settings-section-CfI9nNSa.js +0 -1
  207. package/dist/web/assets/ai-settings-section-CfI9nNSa.js.br +0 -0
  208. package/dist/web/assets/ai-settings-section-CfI9nNSa.js.gz +0 -0
  209. package/dist/web/assets/appearance-settings-section-DRWpbQqj.js.br +0 -0
  210. package/dist/web/assets/appearance-settings-section-DRWpbQqj.js.gz +0 -0
  211. package/dist/web/assets/branch-review-tab-B3BMVCdK.js.br +0 -0
  212. package/dist/web/assets/branch-review-tab-B3BMVCdK.js.gz +0 -0
  213. package/dist/web/assets/chat-tab-B0g-TzDa.js +0 -15
  214. package/dist/web/assets/chat-tab-B0g-TzDa.js.br +0 -0
  215. package/dist/web/assets/chat-tab-B0g-TzDa.js.gz +0 -0
  216. package/dist/web/assets/code-editor-DXra_Rnt.js +0 -7
  217. package/dist/web/assets/code-editor-DXra_Rnt.js.br +0 -0
  218. package/dist/web/assets/code-editor-DXra_Rnt.js.gz +0 -0
  219. package/dist/web/assets/codex-daily-guard-DjLeZ_hE.js.br +0 -0
  220. package/dist/web/assets/codex-daily-guard-DjLeZ_hE.js.gz +0 -0
  221. package/dist/web/assets/conflict-editor-Dx8MnO6j.js.br +0 -0
  222. package/dist/web/assets/conflict-editor-Dx8MnO6j.js.gz +0 -0
  223. package/dist/web/assets/csv-preview-U-Fy9KPT.js.br +0 -0
  224. package/dist/web/assets/csv-preview-U-Fy9KPT.js.gz +0 -0
  225. package/dist/web/assets/database-viewer-v5S-zzB6.js +0 -1
  226. package/dist/web/assets/database-viewer-v5S-zzB6.js.br +0 -0
  227. package/dist/web/assets/database-viewer-v5S-zzB6.js.gz +0 -0
  228. package/dist/web/assets/diff-viewer-Bv2J64t8.js.br +0 -0
  229. package/dist/web/assets/diff-viewer-Bv2J64t8.js.gz +0 -0
  230. package/dist/web/assets/editor-language-service-C1J6iorq.js.br +0 -0
  231. package/dist/web/assets/editor-language-service-C1J6iorq.js.gz +0 -0
  232. package/dist/web/assets/explorer-body-BpTOY2kH.js +0 -2
  233. package/dist/web/assets/explorer-body-BpTOY2kH.js.br +0 -0
  234. package/dist/web/assets/explorer-body-BpTOY2kH.js.gz +0 -0
  235. package/dist/web/assets/explorer-window-content-CZIwX6cE.js.br +0 -0
  236. package/dist/web/assets/explorer-window-content-CZIwX6cE.js.gz +0 -0
  237. package/dist/web/assets/extension-manager-section-DGSKz5iJ.js.br +0 -0
  238. package/dist/web/assets/extension-manager-section-DGSKz5iJ.js.gz +0 -0
  239. package/dist/web/assets/extension-webview-FuuXP0Aw.js.br +0 -0
  240. package/dist/web/assets/extension-webview-FuuXP0Aw.js.gz +0 -0
  241. package/dist/web/assets/files-settings-section-CGd2yfzl.js.br +0 -0
  242. package/dist/web/assets/files-settings-section-CGd2yfzl.js.gz +0 -0
  243. package/dist/web/assets/general-settings-section-CcG10nJq.js.br +0 -0
  244. package/dist/web/assets/general-settings-section-CcG10nJq.js.gz +0 -0
  245. package/dist/web/assets/git-log-panel-B-aSlSlu.js.br +0 -0
  246. package/dist/web/assets/git-log-panel-B-aSlSlu.js.gz +0 -0
  247. package/dist/web/assets/glide-data-grid-usB_4gTg.js.br +0 -0
  248. package/dist/web/assets/glide-data-grid-usB_4gTg.js.gz +0 -0
  249. package/dist/web/assets/group-chat-tab-BAMi_dhb.js +0 -1
  250. package/dist/web/assets/group-chat-tab-BAMi_dhb.js.br +0 -0
  251. package/dist/web/assets/group-chat-tab-BAMi_dhb.js.gz +0 -0
  252. package/dist/web/assets/index-CoTvom0K.js +0 -52
  253. package/dist/web/assets/index-CoTvom0K.js.br +0 -0
  254. package/dist/web/assets/index-CoTvom0K.js.gz +0 -0
  255. package/dist/web/assets/index-DLn9-Lrb.css +0 -1
  256. package/dist/web/assets/index-DLn9-Lrb.css.br +0 -0
  257. package/dist/web/assets/index-DLn9-Lrb.css.gz +0 -0
  258. package/dist/web/assets/jira-watcher-section-cDMBzIR7.js.br +0 -3
  259. package/dist/web/assets/jira-watcher-section-cDMBzIR7.js.gz +0 -0
  260. package/dist/web/assets/keybindings-store-D7nwnInl.js +0 -1
  261. package/dist/web/assets/keyboard-shortcuts-section-CIcdYToP.js.br +0 -0
  262. package/dist/web/assets/keyboard-shortcuts-section-CIcdYToP.js.gz +0 -0
  263. package/dist/web/assets/markdown-renderer-KTNg80_A.js.br +0 -0
  264. package/dist/web/assets/mobile-explorer-sheet-cnwXe-Uz.js.br +0 -0
  265. package/dist/web/assets/mobile-explorer-sheet-cnwXe-Uz.js.gz +0 -0
  266. package/dist/web/assets/notification-store-Bayjh6Js.js +0 -1
  267. package/dist/web/assets/ppmbot-settings-section-CDp69sHV.js.br +0 -0
  268. package/dist/web/assets/ppmbot-settings-section-CDp69sHV.js.gz +0 -0
  269. package/dist/web/assets/problems-panel-BiE3c-31.js.br +0 -0
  270. package/dist/web/assets/problems-panel-BiE3c-31.js.gz +0 -0
  271. package/dist/web/assets/process-table-DcEq9HCl.js.br +0 -0
  272. package/dist/web/assets/process-table-DcEq9HCl.js.gz +0 -0
  273. package/dist/web/assets/proxy-settings-section-P_ULFRpZ.js.br +0 -0
  274. package/dist/web/assets/proxy-settings-section-P_ULFRpZ.js.gz +0 -0
  275. package/dist/web/assets/remote-desktop-clipboard-notice-COjdmADR.js.br +0 -0
  276. package/dist/web/assets/remote-desktop-clipboard-notice-COjdmADR.js.gz +0 -0
  277. package/dist/web/assets/remote-desktop-mobile-sheet-lCU5l2w4.js.br +0 -0
  278. package/dist/web/assets/remote-desktop-mobile-sheet-lCU5l2w4.js.gz +0 -0
  279. package/dist/web/assets/remote-desktop-mobile-view-Bo-wgZPC.js.br +0 -0
  280. package/dist/web/assets/remote-desktop-mobile-view-Bo-wgZPC.js.gz +0 -0
  281. package/dist/web/assets/remote-desktop-readiness-gate-Cqz8kqWG.js.br +0 -0
  282. package/dist/web/assets/remote-desktop-readiness-gate-Cqz8kqWG.js.gz +0 -0
  283. package/dist/web/assets/remote-desktop-window-content-R-aRW07B.js +0 -1
  284. package/dist/web/assets/remote-desktop-window-content-R-aRW07B.js.br +0 -0
  285. package/dist/web/assets/remote-desktop-window-content-R-aRW07B.js.gz +0 -0
  286. package/dist/web/assets/schedules-settings-section-DHs84DvG.js.br +0 -0
  287. package/dist/web/assets/schedules-settings-section-DHs84DvG.js.gz +0 -0
  288. package/dist/web/assets/settings-body-CLt6iutX.js.br +0 -0
  289. package/dist/web/assets/settings-body-CLt6iutX.js.gz +0 -0
  290. package/dist/web/assets/settings-store-tWrnlyEW.js +0 -2
  291. package/dist/web/assets/settings-store-tWrnlyEW.js.br +0 -0
  292. package/dist/web/assets/settings-store-tWrnlyEW.js.gz +0 -0
  293. package/dist/web/assets/sql-query-editor-4Okd-7ss.js.br +0 -0
  294. package/dist/web/assets/sql-query-editor-4Okd-7ss.js.gz +0 -0
  295. package/dist/web/assets/sqlite-viewer-BqDUckIJ.js.br +0 -0
  296. package/dist/web/assets/sqlite-viewer-BqDUckIJ.js.gz +0 -0
  297. package/dist/web/assets/system-monitor-body-Ssmt-r5H.js.br +0 -0
  298. package/dist/web/assets/system-monitor-body-Ssmt-r5H.js.gz +0 -0
  299. package/dist/web/assets/tab-host-window-content--BVyPR1f.js.br +0 -0
  300. package/dist/web/assets/tab-host-window-content--BVyPR1f.js.gz +0 -0
  301. package/dist/web/assets/team-member-sheet-CE2eHuFX.js.br +0 -0
  302. package/dist/web/assets/team-member-sheet-CE2eHuFX.js.gz +0 -0
  303. package/dist/web/assets/team-member-window-content-B1sg0R4H.js.br +0 -0
  304. package/dist/web/assets/team-member-window-content-B1sg0R4H.js.gz +0 -0
  305. package/dist/web/assets/terminal-tab-BrdS4bAy.js.br +0 -0
  306. package/dist/web/assets/terminal-tab-BrdS4bAy.js.gz +0 -0
  307. package/dist/web/assets/tool-cards-DzYJVhjl.js.br +0 -0
  308. package/dist/web/assets/tool-cards-DzYJVhjl.js.gz +0 -0
  309. package/dist/web/assets/use-inline-blame-CKu-Sd-l.js.br +0 -0
  310. package/dist/web/assets/use-inline-blame-CKu-Sd-l.js.gz +0 -0
  311. package/dist/web/assets/use-lsp-BaG0NUXY.js.br +0 -0
  312. package/dist/web/assets/use-lsp-BaG0NUXY.js.gz +0 -0
  313. package/dist/web/assets/video-preview-ZyRR49Lr.js +0 -1
  314. package/dist/web/assets/video-preview-ZyRR49Lr.js.br +0 -0
  315. package/dist/web/assets/video-preview-ZyRR49Lr.js.gz +0 -0
@@ -0,0 +1,196 @@
1
+ /**
2
+ * Sign in to one MCP server. Shared by the chat's sign-in bar and the AI Resources list.
3
+ * Dialog on desktop, bottom sheet on mobile.
4
+ */
5
+ import { useState } from "react";
6
+ import { CheckCircle2, ExternalLink, Loader2, TriangleAlert } from "@/lib/icons";
7
+ import { Button } from "@/components/ui/button";
8
+ import { Input } from "@/components/ui/input";
9
+ import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog";
10
+ import { BottomSheet } from "@/components/ui/mobile-bottom-sheet";
11
+ import { useIsMobile } from "@/hooks/use-is-mobile";
12
+ import { useMcpSignInFlow } from "./use-mcp-sign-in-flow";
13
+ import { CLAUDE_AI_CONNECTORS_URL, isClaudeAiConnector } from "./claude-ai-connector";
14
+
15
+ interface McpSignInDialogProps {
16
+ /** Server to sign in to; null keeps the dialog closed. */
17
+ serverName: string | null;
18
+ /** Project whose MCP configuration applies — matters for project-scoped servers. */
19
+ project?: string;
20
+ onClose: () => void;
21
+ onSignedIn?: (serverName: string) => void;
22
+ }
23
+
24
+ export function McpSignInDialog({ serverName, project, onClose, onSignedIn }: McpSignInDialogProps) {
25
+ const isMobile = useIsMobile();
26
+ const open = serverName !== null;
27
+ const title = serverName ? `Sign in to ${serverName}` : "Sign in";
28
+ const connector = serverName !== null && isClaudeAiConnector(serverName);
29
+ // The flow lives here, above the dialog/sheet switch: the two shells are different
30
+ // trees, and crossing the breakpoint mid sign-in (rotating a large phone) would
31
+ // otherwise remount the body, cancel the flow and kill the CLI's callback listener.
32
+ const flow = useMcpSignInFlow(connector ? null : serverName, project, onSignedIn);
33
+ const body = !serverName ? null : connector
34
+ ? <ClaudeAiConnectorBody onClose={onClose} />
35
+ : <SignInBody key={serverName} serverName={serverName} flowState={flow} onClose={onClose} />;
36
+
37
+ if (isMobile) {
38
+ return (
39
+ <BottomSheet open={open} onClose={onClose} className="popover-solid">
40
+ <div className="px-4 pb-4 pt-1">
41
+ <h2 className="mb-3 text-base font-semibold">{title}</h2>
42
+ {body}
43
+ </div>
44
+ </BottomSheet>
45
+ );
46
+ }
47
+
48
+ return (
49
+ <Dialog open={open} onOpenChange={(o) => { if (!o) onClose(); }}>
50
+ <DialogContent className="sm:max-w-md">
51
+ <DialogHeader>
52
+ <DialogTitle>{title}</DialogTitle>
53
+ <DialogDescription>Grant PPM's Claude sessions access to this MCP server.</DialogDescription>
54
+ </DialogHeader>
55
+ {body}
56
+ </DialogContent>
57
+ </Dialog>
58
+ );
59
+ }
60
+
61
+ function SignInBody({ serverName, flowState, onClose }: {
62
+ serverName: string;
63
+ flowState: ReturnType<typeof useMcpSignInFlow>;
64
+ onClose: () => void;
65
+ }) {
66
+ const { flow, starting, busy, error, retry, submitCallback, confirm } = flowState;
67
+ const [pasted, setPasted] = useState("");
68
+
69
+ if (starting || (!flow && !error)) {
70
+ return <StatusLine icon={<Loader2 className="size-4 animate-spin" />} text="Preparing the sign-in…" />;
71
+ }
72
+
73
+ if (!flow || flow.status === "failed" || flow.status === "expired" || flow.status === "cancelled") {
74
+ return (
75
+ <div className="space-y-3">
76
+ <ErrorLine text={flow?.error ?? error ?? "The sign-in did not finish."} />
77
+ <div className="flex gap-2">
78
+ <Button className="h-11 flex-1 md:h-9" onClick={() => void retry()}>Try again</Button>
79
+ <Button variant="outline" className="h-11 md:h-9" onClick={onClose}>Close</Button>
80
+ </div>
81
+ </div>
82
+ );
83
+ }
84
+
85
+ if (flow.status === "done") {
86
+ return (
87
+ <div className="space-y-3">
88
+ <StatusLine
89
+ icon={<CheckCircle2 className="size-4 text-success" />}
90
+ text={`Signed in. ${serverName} is available to Claude — open chats reconnect it on their own.`}
91
+ />
92
+ <Button className="h-11 w-full md:h-9" onClick={onClose}>Done</Button>
93
+ </div>
94
+ );
95
+ }
96
+
97
+ const shownError = flow.error ?? error;
98
+ return (
99
+ <div className="space-y-4">
100
+ {flow.authUrl && (
101
+ <Button asChild className="h-11 w-full md:h-9">
102
+ {/* A real link, not window.open(): a popup opened after an await is blocked. */}
103
+ <a href={flow.authUrl} target="_blank" rel="noopener noreferrer">
104
+ <ExternalLink className="size-4" /> Open sign-in page
105
+ </a>
106
+ </Button>
107
+ )}
108
+
109
+ {flow.callbackExpected ? (
110
+ <StatusLine
111
+ icon={<Loader2 className="size-4 animate-spin" />}
112
+ text={flow.redirectScheme === "custom"
113
+ ? "Waiting for you to approve access. The page returns to PPM by itself."
114
+ : "Waiting for you to approve access in the browser…"}
115
+ />
116
+ ) : (
117
+ <div className="space-y-2">
118
+ <p className="text-sm text-text-secondary">Grant access on the page that opens, then come back here.</p>
119
+ <Button variant="outline" className="h-11 w-full md:h-9" disabled={busy} onClick={() => void confirm()}>
120
+ {busy ? <Loader2 className="size-4 animate-spin" /> : null} I've granted access
121
+ </Button>
122
+ </div>
123
+ )}
124
+
125
+ {flow.callbackExpected && (
126
+ <form
127
+ className="space-y-2 rounded-md border border-border p-3"
128
+ onSubmit={(e) => { e.preventDefault(); if (pasted.trim()) void submitCallback(pasted.trim()); }}
129
+ >
130
+ <p className="text-xs text-text-secondary">
131
+ {flow.redirectScheme === "custom" ? (
132
+ <>Did the page not come back to PPM? Copy the address the browser ended on after approving and paste it here.</>
133
+ ) : (
134
+ <>
135
+ Not on the machine running PPM? After approving, the browser lands on a <code>localhost</code> page
136
+ that does not load. Copy that page's address and paste it here.
137
+ </>
138
+ )}
139
+ </p>
140
+ <div className="flex gap-2">
141
+ <Input
142
+ value={pasted}
143
+ onChange={(e) => setPasted(e.target.value)}
144
+ placeholder={flow.redirectScheme === "custom" ? "https://…/callback?code=…" : "http://localhost:…/callback?code=…"}
145
+ className="h-11 flex-1 md:h-9"
146
+ autoComplete="off"
147
+ spellCheck={false}
148
+ />
149
+ <Button type="submit" variant="outline" className="h-11 md:h-9" disabled={busy || !pasted.trim()}>
150
+ {busy ? <Loader2 className="size-4 animate-spin" /> : "Submit"}
151
+ </Button>
152
+ </div>
153
+ </form>
154
+ )}
155
+
156
+ {shownError && <ErrorLine text={shownError} />}
157
+ </div>
158
+ );
159
+ }
160
+
161
+ function ClaudeAiConnectorBody({ onClose }: { onClose: () => void }) {
162
+ return (
163
+ <div className="space-y-3">
164
+ <p className="text-sm text-text-secondary">
165
+ This connector belongs to your claude.ai account. Connect it in claude.ai's connector settings,
166
+ then start a new chat to use it here.
167
+ </p>
168
+ <div className="flex gap-2">
169
+ <Button asChild className="h-11 flex-1 md:h-9">
170
+ <a href={CLAUDE_AI_CONNECTORS_URL} target="_blank" rel="noopener noreferrer">
171
+ <ExternalLink className="size-4" /> Open claude.ai connectors
172
+ </a>
173
+ </Button>
174
+ <Button variant="outline" className="h-11 md:h-9" onClick={onClose}>Close</Button>
175
+ </div>
176
+ </div>
177
+ );
178
+ }
179
+
180
+ function StatusLine({ icon, text }: { icon: React.ReactNode; text: string }) {
181
+ return (
182
+ <div className="flex items-start gap-2 text-sm text-text-secondary">
183
+ <span className="mt-0.5 shrink-0">{icon}</span>
184
+ <span>{text}</span>
185
+ </div>
186
+ );
187
+ }
188
+
189
+ function ErrorLine({ text }: { text: string }) {
190
+ return (
191
+ <div className="flex items-start gap-2 text-sm text-error">
192
+ <TriangleAlert className="mt-0.5 size-4 shrink-0" />
193
+ <span className="min-w-0 break-words">{text}</span>
194
+ </div>
195
+ );
196
+ }
@@ -0,0 +1,110 @@
1
+ import { useCallback, useEffect, useRef, useState } from "react";
2
+ import { KeyRound, Loader2, RefreshCw } from "@/lib/icons";
3
+ import { getMcpAuthStatus, type McpServerState } from "@/lib/api-mcp-auth";
4
+ import { McpSignInDialog } from "./mcp-sign-in-dialog";
5
+ import { isClaudeAiConnector } from "./claude-ai-connector";
6
+
7
+ /**
8
+ * Where a server was configured, in words. "dynamic" — what the CLI calls every server
9
+ * PPM hands it — says nothing to the reader, so it is left out.
10
+ */
11
+ function originLabel(s: McpServerState): string | null {
12
+ if (isClaudeAiConnector(s.name, s.scope)) return "claude.ai connector";
13
+ if (s.source === "plugin") return "Plugin";
14
+ if (s.scope && s.scope !== "dynamic") return s.scope;
15
+ return null;
16
+ }
17
+
18
+ interface McpSignInListProps {
19
+ /** Project whose MCP configuration applies; undefined = the home directory's. */
20
+ project?: string;
21
+ /** The panel's search box — the list filters with everything else. */
22
+ search?: string;
23
+ /** Keep the heading and say so when nothing needs a sign-in (Settings), instead of rendering nothing. */
24
+ showWhenEmpty?: boolean;
25
+ }
26
+
27
+ /**
28
+ * MCP servers that are configured but waiting for a sign-in, as a chat in `project` would
29
+ * see them. Covers every source Claude loads — PPM's own list, ~/.claude.json and plugins —
30
+ * which is why it asks the server instead of reading the PPM list next to it.
31
+ */
32
+ export function McpSignInList({ project, search = "", showWhenEmpty = false }: McpSignInListProps) {
33
+ const [servers, setServers] = useState<McpServerState[]>([]);
34
+ const [loading, setLoading] = useState(true);
35
+ const [signingIn, setSigningIn] = useState<string | null>(null);
36
+
37
+ // Only the latest request may write: switching projects quickly must not leave the
38
+ // previous project's answer on screen.
39
+ const seqRef = useRef(0);
40
+ const load = useCallback(async (fresh = false) => {
41
+ const seq = ++seqRef.current;
42
+ setLoading(true);
43
+ try {
44
+ const next = await getMcpAuthStatus(project, fresh);
45
+ if (seq === seqRef.current) setServers(next);
46
+ } catch {
47
+ // A failed probe hides the list rather than showing a stale one.
48
+ if (seq === seqRef.current) setServers([]);
49
+ } finally {
50
+ if (seq === seqRef.current) setLoading(false);
51
+ }
52
+ }, [project]);
53
+
54
+ useEffect(() => { void load(); }, [load]);
55
+
56
+ const q = search.trim().toLowerCase();
57
+ const pending = servers.filter((s) => s.status === "needs-auth" && (!q || s.name.toLowerCase().includes(q)));
58
+
59
+ return (
60
+ <>
61
+ {(pending.length > 0 || showWhenEmpty) && (
62
+ <div className="pb-1">
63
+ <div className="flex items-center gap-1 px-2 py-1 text-[10px] font-semibold uppercase tracking-wide text-text-subtle">
64
+ <span>Needs sign-in</span>
65
+ <span className="text-text-subtle/60">{pending.length}</span>
66
+ <button
67
+ type="button"
68
+ onClick={() => void load(true)}
69
+ disabled={loading}
70
+ className="ml-auto flex size-6 items-center justify-center rounded hover:bg-surface-elevated disabled:opacity-50 max-md:size-9"
71
+ aria-label="Check again"
72
+ title="Check again"
73
+ >
74
+ {loading ? <Loader2 className="size-3 animate-spin" /> : <RefreshCw className="size-3" />}
75
+ </button>
76
+ </div>
77
+ {pending.length === 0 && (
78
+ <p className="px-2 py-1.5 text-[11px] text-text-subtle">
79
+ {loading ? "Checking MCP servers…" : "No MCP server is waiting for a sign-in."}
80
+ </p>
81
+ )}
82
+ {pending.map((s) => (
83
+ <div key={s.name} className="flex items-center gap-2 px-2 py-1.5">
84
+ <KeyRound className="size-4 shrink-0 text-warning" />
85
+ <span className="min-w-0 flex-1">
86
+ <span className="block truncate text-[13px] font-medium">{s.name}</span>
87
+ {originLabel(s) && (
88
+ <span className="block truncate text-[11px] text-text-subtle">{originLabel(s)}</span>
89
+ )}
90
+ </span>
91
+ <button
92
+ type="button"
93
+ onClick={() => setSigningIn(s.name)}
94
+ className="shrink-0 rounded-md bg-primary px-2.5 py-1 text-[11px] font-medium text-primary-foreground hover:bg-primary/90 max-md:min-h-9 max-md:px-3"
95
+ >
96
+ {isClaudeAiConnector(s.name, s.scope) ? "Connect" : "Sign in"}
97
+ </button>
98
+ </div>
99
+ ))}
100
+ </div>
101
+ )}
102
+ <McpSignInDialog
103
+ serverName={signingIn}
104
+ project={project}
105
+ onClose={() => setSigningIn(null)}
106
+ onSignedIn={() => void load(true)}
107
+ />
108
+ </>
109
+ );
110
+ }
@@ -0,0 +1,120 @@
1
+ import { useCallback, useEffect, useRef, useState } from "react";
2
+ import {
3
+ startMcpAuth, getMcpAuthFlow, submitMcpAuthCallback, confirmMcpAuth, cancelMcpAuth,
4
+ type McpAuthFlow,
5
+ } from "@/lib/api-mcp-auth";
6
+ import { useSettingsStore } from "@/stores/settings-store";
7
+
8
+ const POLL_MS = 2_000;
9
+
10
+ function isOpen(flow: McpAuthFlow | null): boolean {
11
+ return flow?.status === "waiting" || flow?.status === "completing";
12
+ }
13
+
14
+ /**
15
+ * Drives one MCP sign-in while `serverName` is set: starts the flow, polls it until it
16
+ * settles, and cancels it if the caller goes away first — an abandoned flow would otherwise
17
+ * hold a Claude subprocess open for its full ten minutes.
18
+ */
19
+ export function useMcpSignInFlow(
20
+ serverName: string | null,
21
+ project: string | undefined,
22
+ onSignedIn?: (serverName: string) => void,
23
+ ) {
24
+ const [flow, setFlow] = useState<McpAuthFlow | null>(null);
25
+ const [starting, setStarting] = useState(false);
26
+ const [busy, setBusy] = useState(false);
27
+ const [error, setError] = useState<string | null>(null);
28
+ const flowRef = useRef<McpAuthFlow | null>(null);
29
+ const onSignedInRef = useRef(onSignedIn);
30
+ onSignedInRef.current = onSignedIn;
31
+ const reportedRef = useRef<string | null>(null);
32
+
33
+ const apply = useCallback((next: McpAuthFlow) => {
34
+ const current = flowRef.current;
35
+ // A poll that left before a submit settled the flow can land after it; an outcome
36
+ // never goes back to "waiting".
37
+ if (current?.id === next.id && !isOpen(current) && isOpen(next)) return;
38
+ flowRef.current = next;
39
+ setFlow(next);
40
+ if (next.status === "done" && reportedRef.current !== next.id) {
41
+ reportedRef.current = next.id;
42
+ // Signed in, so no longer a server the user chose to ignore: if it ever needs a
43
+ // sign-in again, the chat bar should say so.
44
+ useSettingsStore.getState().undismissMcpSignIn(next.serverName);
45
+ onSignedInRef.current?.(next.serverName);
46
+ }
47
+ }, []);
48
+
49
+ // Bumped by every start and every teardown, so a start that resolves late — StrictMode
50
+ // runs the opening effect twice, and the server cancels the first flow when the second
51
+ // begins — can neither overwrite the live flow nor outlive the dialog.
52
+ const genRef = useRef(0);
53
+
54
+ const start = useCallback(async () => {
55
+ if (!serverName) return;
56
+ const gen = ++genRef.current;
57
+ setStarting(true);
58
+ setError(null);
59
+ setFlow(null);
60
+ flowRef.current = null;
61
+ try {
62
+ const next = await startMcpAuth(serverName, project);
63
+ if (gen !== genRef.current) {
64
+ if (isOpen(next)) void cancelMcpAuth(next.id).catch(() => {});
65
+ return;
66
+ }
67
+ apply(next);
68
+ } catch (e) {
69
+ if (gen === genRef.current) setError((e as Error).message);
70
+ } finally {
71
+ if (gen === genRef.current) setStarting(false);
72
+ }
73
+ }, [serverName, project, apply]);
74
+
75
+ // Start when opened; cancel whatever is still open when closed or unmounted.
76
+ useEffect(() => {
77
+ if (!serverName) return;
78
+ void start();
79
+ return () => {
80
+ genRef.current++;
81
+ const open = flowRef.current;
82
+ if (open && isOpen(open)) void cancelMcpAuth(open.id).catch(() => {});
83
+ flowRef.current = null;
84
+ };
85
+ }, [serverName, start]);
86
+
87
+ const flowId = flow?.id;
88
+ const polling = isOpen(flow);
89
+ useEffect(() => {
90
+ if (!flowId || !polling) return;
91
+ const timer = setInterval(() => {
92
+ getMcpAuthFlow(flowId).then(apply).catch((e) => {
93
+ // The server no longer knows this flow (restarted, or it was cancelled elsewhere):
94
+ // end it here too, so the dialog offers "Try again" instead of spinning forever.
95
+ const current = flowRef.current;
96
+ if (current?.id === flowId) apply({ ...current, status: "failed", authUrl: undefined, error: (e as Error).message });
97
+ });
98
+ }, POLL_MS);
99
+ return () => clearInterval(timer);
100
+ }, [flowId, polling, apply]);
101
+
102
+ const run = useCallback(async (action: (id: string) => Promise<McpAuthFlow>) => {
103
+ const current = flowRef.current;
104
+ if (!current) return;
105
+ setBusy(true);
106
+ setError(null);
107
+ try {
108
+ apply(await action(current.id));
109
+ } catch (e) {
110
+ setError((e as Error).message);
111
+ } finally {
112
+ setBusy(false);
113
+ }
114
+ }, [apply]);
115
+
116
+ const submitCallback = useCallback((url: string) => run((id) => submitMcpAuthCallback(id, url)), [run]);
117
+ const confirm = useCallback(() => run(confirmMcpAuth), [run]);
118
+
119
+ return { flow, starting, busy, error, retry: start, submitCallback, confirm };
120
+ }
@@ -15,6 +15,7 @@ import { api } from "@/lib/api-client";
15
15
  import { ProviderBadge } from "@/components/chat/provider-selector";
16
16
  import { openSettings } from "./open-settings";
17
17
  import { CodexContextSettings } from "./codex-context-settings";
18
+ import { McpSignInList } from "@/components/mcp-auth/mcp-sign-in-list";
18
19
  import type { ModelOption } from "../../../types/chat";
19
20
 
20
21
  const EFFORT_OPTIONS = [
@@ -394,6 +395,16 @@ export function AISettingsSection({ compact }: { compact?: boolean } = {}) {
394
395
  onCheckedChange={(v) => handleSave("inherit_claude_mcp", v)}
395
396
  />
396
397
  </div>
398
+
399
+ <div className={fieldGap}>
400
+ <Label className={compact ? labelSize : undefined}>MCP sign-in</Label>
401
+ <p className={`${compact ? "text-[9px]" : "text-[11px]"} text-muted-foreground`}>
402
+ Servers that use OAuth stay unavailable to Claude until someone signs in. Project-scoped servers are listed in the project's AI Resources panel.
403
+ </p>
404
+ <div className="-mx-2">
405
+ <McpSignInList showWhenEmpty />
406
+ </div>
407
+ </div>
397
408
  </>
398
409
  )}
399
410
 
@@ -65,6 +65,8 @@ interface UseChatReturn {
65
65
  pendingApproval: ApprovalRequest | null;
66
66
  contextWindowPct: number | null;
67
67
  compactStatus: "compacting" | null;
68
+ /** MCP servers this session's subprocess reported as needing a sign-in. */
69
+ mcpNeedsAuth: string[];
68
70
  statusMessage: string | null;
69
71
  sessionTitle: string | null;
70
72
  /**
@@ -150,6 +152,8 @@ export function useChat(
150
152
  const [pendingApproval, setPendingApproval] = useState<ApprovalRequest | null>(null);
151
153
  const [contextWindowPct, setContextWindowPct] = useState<number | null>(null);
152
154
  const [compactStatus, setCompactStatus] = useState<"compacting" | null>(null);
155
+ /** MCP servers this session's subprocess reported as needing a sign-in. */
156
+ const [mcpNeedsAuth, setMcpNeedsAuth] = useState<string[]>([]);
153
157
  const [backgroundShells, setBackgroundShells] = useState<BackgroundShell[]>([]);
154
158
  const backgroundShellsRef = useRef<BackgroundShell[]>([]);
155
159
  const [statusMessage, setStatusMessage] = useState<string | null>(null);
@@ -862,6 +866,12 @@ export function useChat(
862
866
  return;
863
867
  }
864
868
 
869
+ if ((data as any).type === "mcp_status") {
870
+ const list = (data as any).needsAuth;
871
+ setMcpNeedsAuth(Array.isArray(list) ? list : []);
872
+ return;
873
+ }
874
+
865
875
  // Handle compact status events
866
876
  if ((data as any).type === "compact_status") {
867
877
  const status = (data as any).status;
@@ -951,6 +961,7 @@ export function useChat(
951
961
  // Sync compact indicator from authoritative server state (covers reconnect).
952
962
  // state.compactStatus is "compacting" | null — treat undefined as null for back-compat.
953
963
  setCompactStatus(state.compactStatus === "compacting" ? "compacting" : null);
964
+ setMcpNeedsAuth(Array.isArray(state.mcpNeedsAuth) ? state.mcpNeedsAuth : []);
954
965
  // If idle, refetch history (completed turns) and hide overlay.
955
966
  // Skip when nothing could have changed: the phase was already idle locally
956
967
  // and the full transcript finished loading moments ago — on boot the WS
@@ -1109,6 +1120,8 @@ export function useChat(
1109
1120
  setPendingApproval(null);
1110
1121
  if (approvalToastRef.current != null) { toast.dismiss(approvalToastRef.current); approvalToastRef.current = null; }
1111
1122
  setCompactStatus(null);
1123
+ // Another session's subprocess saw another MCP state; its session_state brings its own.
1124
+ setMcpNeedsAuth([]);
1112
1125
  // Clear ephemeral pre-compact expansions on session change
1113
1126
  setExpansions(new Map());
1114
1127
  // Drop the previous session's version groups. Keeping them would let the
@@ -1516,6 +1529,7 @@ export function useChat(
1516
1529
  pendingApproval,
1517
1530
  contextWindowPct,
1518
1531
  compactStatus,
1532
+ mcpNeedsAuth,
1519
1533
  statusMessage,
1520
1534
  sessionTitle,
1521
1535
  /** Account the server last reported for this session — beats the polled usage label. */
@@ -0,0 +1,54 @@
1
+ import { api } from "./api-client";
2
+
3
+ export type McpAuthFlowStatus = "waiting" | "completing" | "done" | "failed" | "expired" | "cancelled";
4
+
5
+ export interface McpAuthFlow {
6
+ id: string;
7
+ serverName: string;
8
+ status: McpAuthFlowStatus;
9
+ /** Open this to sign in. Absent once the flow has settled. */
10
+ authUrl?: string;
11
+ /** False for a claude.ai connector: nothing redirects back, the user confirms instead. */
12
+ callbackExpected: boolean;
13
+ /** "localhost": the browser returns to the host machine's loopback, which only works on that machine. */
14
+ redirectScheme?: "localhost" | "custom";
15
+ error?: string;
16
+ }
17
+
18
+ export interface McpServerState {
19
+ name: string;
20
+ status: string;
21
+ error?: string;
22
+ scope?: string;
23
+ source?: string;
24
+ }
25
+
26
+ export function getMcpAuthStatus(project?: string, fresh = false): Promise<McpServerState[]> {
27
+ const params = new URLSearchParams();
28
+ if (project) params.set("project", project);
29
+ if (fresh) params.set("fresh", "1");
30
+ const qs = params.toString();
31
+ return api.get<McpServerState[]>(`/api/mcp-auth/status${qs ? `?${qs}` : ""}`);
32
+ }
33
+
34
+ export function startMcpAuth(server: string, project?: string): Promise<McpAuthFlow> {
35
+ // The origin lets the server have the sign-in return to PPM itself, which is what makes
36
+ // it work from a phone; the server only uses it when it matches the request's host.
37
+ return api.post<McpAuthFlow>("/api/mcp-auth/start", { server, project, origin: window.location.origin });
38
+ }
39
+
40
+ export function getMcpAuthFlow(id: string): Promise<McpAuthFlow> {
41
+ return api.get<McpAuthFlow>(`/api/mcp-auth/flows/${encodeURIComponent(id)}`);
42
+ }
43
+
44
+ export function submitMcpAuthCallback(id: string, url: string): Promise<McpAuthFlow> {
45
+ return api.post<McpAuthFlow>(`/api/mcp-auth/flows/${encodeURIComponent(id)}/callback`, { url });
46
+ }
47
+
48
+ export function confirmMcpAuth(id: string): Promise<McpAuthFlow> {
49
+ return api.post<McpAuthFlow>(`/api/mcp-auth/flows/${encodeURIComponent(id)}/confirm`, {});
50
+ }
51
+
52
+ export function cancelMcpAuth(id: string): Promise<void> {
53
+ return api.del(`/api/mcp-auth/flows/${encodeURIComponent(id)}`);
54
+ }
@@ -65,6 +65,10 @@ interface SettingsState {
65
65
  /** User ticked "don't show again" on the remote-desktop warning that precedes every open
66
66
  * (`remote-desktop-warning-gate.tsx`); once true the viewer connects straight away. */
67
67
  remoteDesktopWarningDismissed: boolean;
68
+ /** MCP servers hidden from the chat's sign-in bar. The bar comes back only for a server
69
+ * outside this list; a successful sign-in takes the server off it, so a later expiry
70
+ * is announced again. Synced like other UI prefs. */
71
+ mcpSignInDismissed: string[];
68
72
  /** Hold a screen wake lock while any chat turn is running, so a propped-up tablet does not
69
73
  * dim mid-answer. Defaults on; see `hooks/use-wake-lock.ts`. */
70
74
  keepScreenAwake: boolean;
@@ -148,6 +152,8 @@ interface SettingsState {
148
152
  setExplorerSkin: (pref: ExplorerSkinPref) => void;
149
153
  toggleRemoteDesktopStatsVisible: () => void;
150
154
  setRemoteDesktopWarningDismissed: (dismissed: boolean) => void;
155
+ dismissMcpSignIn: (serverNames: string[]) => void;
156
+ undismissMcpSignIn: (serverName: string) => void;
151
157
  setKeepScreenAwake: (enabled: boolean) => void;
152
158
  fetchServerInfo: () => Promise<void>;
153
159
  /** Re-push the in-memory theme selection to the server (see the action for why). */
@@ -177,6 +183,7 @@ interface PersistedSettings {
177
183
  explorerSkin?: ExplorerSkinPref;
178
184
  remoteDesktopStatsVisible?: boolean;
179
185
  remoteDesktopWarningDismissed?: boolean;
186
+ mcpSignInDismissed?: string[];
180
187
  keepScreenAwake?: boolean;
181
188
  remoteDesktopQuality?: QualityChoice;
182
189
  remoteDesktopViewStyle?: ViewStyle;
@@ -242,6 +249,15 @@ const DEFAULT_DB_EXPANDED: DbSidebarExpanded = { conns: [], groups: ["__ungroupe
242
249
  const DB_EXPANDED_CAPS = { conns: 200, groups: 200, tables: 500 } as const;
243
250
 
244
251
  /** Coerce stored/server data into a valid expansion set; null when unusable. */
252
+ /** Same bound as the server's validator for the pref. */
253
+ const MAX_NAME_LIST = 200;
254
+
255
+ /** A list of names read back from storage or the server, or null when it is not one. */
256
+ function sanitizeNameList(value: unknown): string[] | null {
257
+ if (!Array.isArray(value)) return null;
258
+ return value.filter((s): s is string => typeof s === "string" && s.length > 0 && s.length <= 200).slice(-MAX_NAME_LIST);
259
+ }
260
+
245
261
  function sanitizeDbExpanded(value: unknown): DbSidebarExpanded | null {
246
262
  if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
247
263
  const v = value as Record<string, unknown>;
@@ -356,6 +372,8 @@ function applyServerUiPrefs(data: Record<string, unknown>) {
356
372
  }
357
373
  if (typeof data.remoteDesktopStatsVisible === "boolean") patch.remoteDesktopStatsVisible = data.remoteDesktopStatsVisible;
358
374
  if (typeof data.remoteDesktopWarningDismissed === "boolean") patch.remoteDesktopWarningDismissed = data.remoteDesktopWarningDismissed;
375
+ const mcpDismissed = sanitizeNameList(data.mcpSignInDismissed);
376
+ if (mcpDismissed) patch.mcpSignInDismissed = mcpDismissed;
359
377
  if (typeof data.keepScreenAwake === "boolean") patch.keepScreenAwake = data.keepScreenAwake;
360
378
  const dbExpanded = sanitizeDbExpanded(data.dbSidebarExpanded);
361
379
  if (dbExpanded) patch.dbSidebarExpanded = dbExpanded;
@@ -402,6 +420,7 @@ export const useSettingsStore = create<SettingsState>((set, get) => ({
402
420
  explorerSkin: (_initial.explorerSkin === "windows" || _initial.explorerSkin === "macos") ? _initial.explorerSkin : "auto",
403
421
  remoteDesktopStatsVisible: _initial.remoteDesktopStatsVisible ?? false,
404
422
  remoteDesktopWarningDismissed: _initial.remoteDesktopWarningDismissed ?? false,
423
+ mcpSignInDismissed: sanitizeNameList(_initial.mcpSignInDismissed) ?? [],
405
424
  keepScreenAwake: _initial.keepScreenAwake ?? true,
406
425
  deviceName: null,
407
426
  version: null,
@@ -592,6 +611,21 @@ export const useSettingsStore = create<SettingsState>((set, get) => ({
592
611
  set({ remoteDesktopWarningDismissed: dismissed });
593
612
  },
594
613
 
614
+ dismissMcpSignIn: (serverNames) => {
615
+ const current = get().mcpSignInDismissed;
616
+ const next = [...current, ...serverNames.filter((n) => !current.includes(n))].slice(-MAX_NAME_LIST);
617
+ persistUiPref({ mcpSignInDismissed: next });
618
+ set({ mcpSignInDismissed: next });
619
+ },
620
+
621
+ undismissMcpSignIn: (serverName) => {
622
+ const current = get().mcpSignInDismissed;
623
+ if (!current.includes(serverName)) return;
624
+ const next = current.filter((n) => n !== serverName);
625
+ persistUiPref({ mcpSignInDismissed: next });
626
+ set({ mcpSignInDismissed: next });
627
+ },
628
+
595
629
  setKeepScreenAwake: (enabled) => {
596
630
  persistUiPref({ keepScreenAwake: enabled });
597
631
  set({ keepScreenAwake: enabled });