@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
@@ -1072,14 +1072,7 @@ export class ClaudeAgentSdkProvider implements AIProvider {
1072
1072
  }
1073
1073
  console.log(`[sdk] query: session=${sessionId} isFirst=${isFirstMessage} fork=${shouldFork} cwd=${effectiveCwd} platform=${process.platform} accountMode=${!!account} permissionMode=${permissionMode} isBypass=${isBypass}`);
1074
1074
 
1075
- // Read MCP servers from PPM DB (fresh per query — user may add/remove between chats),
1076
- // merged with servers inherited from Claude Code's ~/.claude.json for this project.
1077
- // PPM DB entries override inherited ones on name conflict.
1078
- const ownServers = mcpConfigService.list();
1079
- const inheritedServers = providerConfig.inherit_claude_mcp !== false
1080
- ? listInheritedClaudeMcpServers(effectiveCwd)
1081
- : {};
1082
- const mcpServers = { ...inheritedServers, ...ownServers };
1075
+ const mcpServers = this.resolveMcpServers(effectiveCwd);
1083
1076
  const hasMcp = Object.keys(mcpServers).length > 0;
1084
1077
 
1085
1078
  // Buffer subprocess stderr for crash diagnostics + log in real-time
@@ -1334,6 +1327,15 @@ export class ClaudeAgentSdkProvider implements AIProvider {
1334
1327
  } else {
1335
1328
  console.log(`[sdk] session=${sessionId} init: sdk_session_id=${sdkSid}`);
1336
1329
  }
1330
+ // Carry each MCP server's connection state so the host can offer a sign-in
1331
+ // for the ones reporting `needs-auth`.
1332
+ const initServers = (msg as any).mcp_servers;
1333
+ yield {
1334
+ type: "system" as any,
1335
+ subtype,
1336
+ ...(Array.isArray(initServers) && { mcpServers: initServers }),
1337
+ } as any;
1338
+ continue;
1337
1339
  }
1338
1340
 
1339
1341
  // Detect compacting status
@@ -2109,6 +2111,83 @@ export class ClaudeAgentSdkProvider implements AIProvider {
2109
2111
  }
2110
2112
 
2111
2113
 
2114
+ /**
2115
+ * MCP servers for a query in `cwd`: PPM's own (read fresh — the user may add or remove
2116
+ * one between chats) over those inherited from Claude Code's ~/.claude.json for this
2117
+ * project. PPM entries win on a name conflict.
2118
+ */
2119
+ private resolveMcpServers(cwd: string): Record<string, unknown> {
2120
+ const inherited = this.getProviderConfig().inherit_claude_mcp !== false
2121
+ ? listInheritedClaudeMcpServers(cwd)
2122
+ : {};
2123
+ return { ...inherited, ...mcpConfigService.list() };
2124
+ }
2125
+
2126
+ /**
2127
+ * A subprocess that never receives a prompt, for control requests only — MCP status
2128
+ * and MCP sign-in. It is configured like a chat query in `cwd` (same CLI, settings
2129
+ * sources and MCP servers), so it sees exactly the servers a chat there would, and it
2130
+ * never calls the model. The caller owns it and must `close()` it: an OAuth flow's
2131
+ * localhost callback listener lives inside this process.
2132
+ */
2133
+ openMcpControlQuery(cwd: string): { query: any; close: () => void } {
2134
+ const providerConfig = this.getProviderConfig();
2135
+ const cliExecutablePath = resolveCliExecutablePath(
2136
+ (providerConfig as { cli_command?: string }).cli_command,
2137
+ );
2138
+ const mcpServers = this.resolveMcpServers(cwd);
2139
+ const { generator, controller } = createMessageChannel();
2140
+ const q = query({
2141
+ prompt: generator,
2142
+ options: {
2143
+ ...(needsNodeInterpreter(process.platform, cliExecutablePath) && { executable: "node" }),
2144
+ ...(cliExecutablePath && { pathToClaudeCodeExecutable: cliExecutablePath }),
2145
+ cwd,
2146
+ settingSources: ["user", "project"],
2147
+ // No account: MCP OAuth tokens belong to the CLI's own credential store, the same
2148
+ // one an interactive `claude` writes, and no model call is ever made here.
2149
+ env: this.buildQueryEnv(cwd, null),
2150
+ settings: { permissions: { allow: [], deny: [] } },
2151
+ ...(Object.keys(mcpServers).length > 0 && { mcpServers }),
2152
+ stderr: (chunk: string) => {
2153
+ const line = chunk.trim();
2154
+ if (line) console.log(`[mcp-control] stderr: ${line.slice(0, 300)}`);
2155
+ },
2156
+ } as any,
2157
+ });
2158
+ let closed = false;
2159
+ return {
2160
+ query: q,
2161
+ close: () => {
2162
+ if (closed) return;
2163
+ closed = true;
2164
+ controller.done();
2165
+ q.close();
2166
+ },
2167
+ };
2168
+ }
2169
+
2170
+ /**
2171
+ * Reconnect one MCP server inside a live chat subprocess, so a sign-in finished
2172
+ * elsewhere shows up without restarting the conversation. Returns the server's status
2173
+ * afterwards, or null when the session has no live subprocess.
2174
+ */
2175
+ async reconnectMcpServer(sessionId: string, serverName: string): Promise<string | null> {
2176
+ const ss = this.streamingSessions.get(sessionId);
2177
+ if (!ss) return null;
2178
+ try {
2179
+ await ss.query.reconnectMcpServer(serverName);
2180
+ } catch (e) {
2181
+ console.warn(`[sdk] session=${sessionId} reconnectMcpServer(${serverName}) failed: ${(e as Error).message}`);
2182
+ }
2183
+ try {
2184
+ const statuses: Array<{ name: string; status: string }> = await ss.query.mcpServerStatus();
2185
+ return statuses.find((s) => s.name === serverName)?.status ?? null;
2186
+ } catch {
2187
+ return null;
2188
+ }
2189
+ }
2190
+
2112
2191
  /** Abort and fully teardown the streaming session — user must resume to continue */
2113
2192
  abortQuery(sessionId: string, source = "unknown"): void {
2114
2193
  // Capture stack to identify caller during debugging intermittent abort bugs
@@ -180,6 +180,12 @@ app.route("/proxy", proxyRoutes);
180
180
  // HTML assets authenticate with bounded preview capabilities, never the PPM session token.
181
181
  app.route("/api/html-preview/content", htmlPreviewRoutes.content);
182
182
 
183
+ // MCP sign-in redirect: reached by a browser navigation from the authorization server,
184
+ // which carries no PPM token. It only completes a flow an authenticated user started.
185
+ import { mcpAuthRoutes, mcpAuthCallbackHandler } from "./routes/mcp-auth.ts";
186
+ import { mcpOAuthFlows } from "../services/mcp-oauth/mcp-oauth-flows.ts";
187
+ app.get("/api/mcp-auth/callback", mcpAuthCallbackHandler);
188
+
183
189
  // Auth check endpoint (behind auth middleware)
184
190
  app.use("/api/*", authMiddleware);
185
191
  app.use("/api/*", gzipJson);
@@ -227,6 +233,7 @@ app.route("/api/loopback", loopbackRoutes);
227
233
  // API routes
228
234
  app.route("/api/settings", settingsRoutes);
229
235
  app.route("/api/settings/mcp", mcpRoutes);
236
+ app.route("/api/mcp-auth", mcpAuthRoutes);
230
237
  app.route("/api/settings/themes", settingsThemesRoutes);
231
238
  app.route("/api/tunnel", tunnelRoutes);
232
239
  import { namedTunnelRoutes } from "./routes/named-tunnel.ts";
@@ -1054,6 +1061,8 @@ if (process.argv.includes("__serve__")) {
1054
1061
  // resident rust-analyzer holding a crate graph is too expensive to leave
1055
1062
  // to chance. Synchronous because process.exit follows immediately.
1056
1063
  try { lspManager.killAllSync(); } catch {}
1064
+ // An MCP sign-in holds a Claude subprocess until it settles.
1065
+ try { mcpOAuthFlows.disposeAll(); } catch {}
1057
1066
  try { server.stop(true); } catch {}
1058
1067
  process.exit(0);
1059
1068
  };
@@ -0,0 +1,117 @@
1
+ /**
2
+ * MCP sign-in (OAuth) for servers that report `needs-auth`.
3
+ *
4
+ * Everything here is behind PPM's auth except `mcpAuthCallbackHandler`: the authorization
5
+ * server redirects the *browser* there by a top-level navigation, which carries no PPM
6
+ * token. It is safe to leave open because it can only complete a flow an authenticated
7
+ * user started — it matches the redirect's single-use OAuth `state` to a waiting flow, and
8
+ * the CLI checks that state again before exchanging the code.
9
+ */
10
+ import { Hono, type Context } from "hono";
11
+ import { homedir } from "node:os";
12
+ import { ok, err } from "../../types/api.ts";
13
+ import { resolveProjectPath } from "../helpers/resolve-project.ts";
14
+ import { mcpOAuthFlows, McpAuthFlowNotFoundError, type McpOAuthFlows } from "../../services/mcp-oauth/mcp-oauth-flows.ts";
15
+ import { mcpStatusProbe } from "../../services/mcp-oauth/mcp-status-probe.ts";
16
+ import { callbackUrlFor, customRedirectUri } from "../../services/mcp-oauth/mcp-oauth-redirect.ts";
17
+
18
+ // A cached status would still say `needs-auth` right after a sign-in.
19
+ mcpOAuthFlows.onAuthorized(() => mcpStatusProbe.invalidate());
20
+
21
+ export const mcpAuthRoutes = new Hono();
22
+
23
+ /** Directory whose MCP configuration applies: the project's, or the home directory. */
24
+ function cwdFor(project: string | undefined): string {
25
+ return project ? resolveProjectPath(project) : homedir();
26
+ }
27
+
28
+ // GET /status?project=<name>&fresh=1 — every server's connection state
29
+ mcpAuthRoutes.get("/status", async (c) => {
30
+ let cwd: string;
31
+ try { cwd = cwdFor(c.req.query("project")); } catch (e) { return c.json(err((e as Error).message), 404); }
32
+ try {
33
+ const servers = await mcpStatusProbe.status(cwd, { fresh: c.req.query("fresh") === "1" });
34
+ return c.json(ok(servers));
35
+ } catch (e) {
36
+ return c.json(err(`Could not read MCP status: ${(e as Error).message}`), 500);
37
+ }
38
+ });
39
+
40
+ // POST /start { server, project?, origin? } — begin a sign-in, returns the flow
41
+ mcpAuthRoutes.post("/start", async (c) => {
42
+ const body = await c.req.json<{ server?: string; project?: string; origin?: string }>().catch(() => ({} as Record<string, undefined>));
43
+ if (!body.server || typeof body.server !== "string") return c.json(err("Missing required field: server"), 400);
44
+ let cwd: string;
45
+ try { cwd = cwdFor(body.project); } catch (e) { return c.json(err((e as Error).message), 404); }
46
+ const redirectUri = customRedirectUri(body.origin, [c.req.header("x-forwarded-host"), c.req.header("host")]);
47
+ try {
48
+ return c.json(ok(await mcpOAuthFlows.start(body.server, cwd, redirectUri)));
49
+ } catch (e) {
50
+ return c.json(err(`Could not start the sign-in: ${(e as Error).message}`), 500);
51
+ }
52
+ });
53
+
54
+ mcpAuthRoutes.get("/flows/:id", (c) => {
55
+ const flow = mcpOAuthFlows.get(c.req.param("id"));
56
+ return flow ? c.json(ok(flow)) : c.json(err("This sign-in is no longer active. Start again."), 404);
57
+ });
58
+
59
+ // POST /flows/:id/callback { url } — the redirect URL the user pasted
60
+ mcpAuthRoutes.post("/flows/:id/callback", async (c) => {
61
+ const body = await c.req.json<{ url?: string }>().catch(() => ({} as { url?: string }));
62
+ if (!body.url || typeof body.url !== "string" || body.url.length > 16_384) {
63
+ return c.json(err("Paste the full address from the browser after signing in."), 400);
64
+ }
65
+ return flowAction(c, () => mcpOAuthFlows.submitCallback(c.req.param("id"), body.url!.trim()));
66
+ });
67
+
68
+ // POST /flows/:id/confirm — "I've granted access" for a claude.ai connector
69
+ mcpAuthRoutes.post("/flows/:id/confirm", (c) => flowAction(c, () => mcpOAuthFlows.confirm(c.req.param("id"))));
70
+
71
+ mcpAuthRoutes.delete("/flows/:id", (c) => {
72
+ const flow = mcpOAuthFlows.cancel(c.req.param("id"));
73
+ return flow ? c.json(ok(flow)) : c.json(err("Sign-in not found"), 404);
74
+ });
75
+
76
+ async function flowAction(c: Context, run: () => Promise<unknown>) {
77
+ try {
78
+ return c.json(ok(await run()));
79
+ } catch (e) {
80
+ if (e instanceof McpAuthFlowNotFoundError) return c.json(err(e.message), 404);
81
+ return c.json(err((e as Error).message), 500);
82
+ }
83
+ }
84
+
85
+ /** GET /api/mcp-auth/callback — PUBLIC. Where the authorization server sends the browser back. */
86
+ export function createMcpAuthCallbackHandler(flows: McpOAuthFlows) {
87
+ return async (c: Context) => {
88
+ const url = new URL(c.req.url);
89
+ const flow = flows.findByState(url.searchParams.get("state"));
90
+ // A flow without a redirect URI uses the CLI's own localhost listener; its state
91
+ // arriving here means somebody is replaying it, not completing it.
92
+ if (!flow || !flow.redirectUri) {
93
+ return c.html(page("Sign-in not found", "This sign-in link is no longer active. Start the sign-in again from PPM."), 404);
94
+ }
95
+ const serverName = flow.serverName;
96
+ const result = await flows.submitCallback(flow.id, callbackUrlFor(flow.redirectUri, url.search));
97
+ if (result.status === "done") {
98
+ return c.html(page("Signed in", `${serverName} is connected. You can close this tab and return to PPM.`, true));
99
+ }
100
+ return c.html(page("Sign-in failed", result.error ?? `Could not finish signing in to ${serverName}.`), 400);
101
+ };
102
+ }
103
+
104
+ export const mcpAuthCallbackHandler = createMcpAuthCallbackHandler(mcpOAuthFlows);
105
+
106
+ function page(title: string, message: string, closeSoon = false): string {
107
+ // Best effort: after the cross-site redirects most browsers refuse to let a tab close
108
+ // itself, and then the message simply stays.
109
+ const script = closeSoon ? "<script>setTimeout(function(){window.close()},1500)</script>" : "";
110
+ return `<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">`
111
+ + `<title>${escapeHtml(title)}</title><style>body{font-family:system-ui,sans-serif;max-width:32rem;margin:15vh auto;padding:0 1.5rem;line-height:1.5;color-scheme:light dark}</style>`
112
+ + `</head><body><h1>${escapeHtml(title)}</h1><p>${escapeHtml(message)}</p>${script}</body></html>`;
113
+ }
114
+
115
+ function escapeHtml(s: string): string {
116
+ return s.replace(/[&<>"']/g, (ch) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[ch]!);
117
+ }
@@ -118,6 +118,8 @@ const UI_PREF_VALIDATORS: Record<string, (v: unknown) => boolean> = {
118
118
  sidebarCollapsed: (v) => typeof v === "boolean",
119
119
  remoteDesktopStatsVisible: (v) => typeof v === "boolean",
120
120
  remoteDesktopWarningDismissed: (v) => typeof v === "boolean",
121
+ // MCP servers the user hid from the chat's sign-in bar (by name)
122
+ mcpSignInDismissed: (v) => Array.isArray(v) && v.length <= 200 && v.every((s) => typeof s === "string" && s.length <= 200),
121
123
  keepScreenAwake: (v) => typeof v === "boolean",
122
124
  sidebarWidth: (v) => typeof v === "number" && v >= 200 && v <= 600,
123
125
  gitStatusViewMode: (v) => v === "flat" || v === "tree",
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Keeps each chat's "needs sign-in" list in step with MCP sign-ins.
3
+ *
4
+ * The list comes from the SDK's `init` message, so it is only as fresh as the subprocess.
5
+ * A sign-in finished from any screen stores tokens every Claude process can read, but a
6
+ * running chat subprocess keeps the server in `needs-auth` until told to reconnect it — so
7
+ * on each sign-in the server is reconnected in every chat still listing it.
8
+ *
9
+ * If that chat's subprocess still reports `needs-auth` afterwards, the only way to make it
10
+ * read the tokens is a fresh subprocess. That is done only when nothing would be lost: an
11
+ * idle turn is not enough, because background agents and shells keep running inside the
12
+ * subprocess after the turn that started them has ended. Otherwise the server goes back on
13
+ * the list, so the bar keeps saying what is still true.
14
+ */
15
+ import { mcpOAuthFlows } from "../../services/mcp-oauth/mcp-oauth-flows.ts";
16
+
17
+ export interface McpSignInSession {
18
+ providerId: string;
19
+ phase: string;
20
+ mcpNeedsAuth?: string[];
21
+ }
22
+
23
+ export interface McpSignInSyncDeps {
24
+ sessions: () => Iterable<[string, McpSignInSession]>;
25
+ broadcast: (sessionId: string, event: unknown) => void;
26
+ reconnect: (providerId: string, sessionId: string, serverName: string) => Promise<string | null>;
27
+ /** Whether the subprocess can be replaced without killing work still running in it. */
28
+ canDrop: (sessionId: string) => boolean;
29
+ dropIdle: (sessionId: string, serverName: string) => void;
30
+ }
31
+
32
+ /** The event carrying a session's list to its clients. */
33
+ export function mcpStatusEvent(needsAuth: string[] | undefined) {
34
+ return { type: "mcp_status", needsAuth: needsAuth ?? [] };
35
+ }
36
+
37
+ /** React to one successful sign-in. Exported for tests; production wires it via `register`. */
38
+ export async function handleMcpAuthorized(serverName: string, deps: McpSignInSyncDeps): Promise<void> {
39
+ const affected = [...deps.sessions()].filter(([, s]) => s.mcpNeedsAuth?.includes(serverName));
40
+ await Promise.all(affected.map(async ([sessionId, s]) => {
41
+ s.mcpNeedsAuth = s.mcpNeedsAuth!.filter((n) => n !== serverName);
42
+ deps.broadcast(sessionId, mcpStatusEvent(s.mcpNeedsAuth));
43
+ const status = await deps.reconnect(s.providerId, sessionId, serverName).catch(() => null);
44
+ // Anything but a definite `needs-auth` (connected, pending, a transient failure, or no
45
+ // live subprocess at all) is left alone: the next `init` reports the truth.
46
+ if (status !== "needs-auth") return;
47
+ if (s.phase === "idle" && deps.canDrop(sessionId)) {
48
+ deps.dropIdle(sessionId, serverName);
49
+ return;
50
+ }
51
+ if (!s.mcpNeedsAuth!.includes(serverName)) s.mcpNeedsAuth = [...s.mcpNeedsAuth!, serverName];
52
+ deps.broadcast(sessionId, mcpStatusEvent(s.mcpNeedsAuth));
53
+ }));
54
+ }
55
+
56
+ export function registerMcpSignInSync(deps: McpSignInSyncDeps): () => void {
57
+ return mcpOAuthFlows.onAuthorized((serverName) => { void handleMcpAuthorized(serverName, deps); });
58
+ }
@@ -18,6 +18,8 @@ import { configService } from "../../services/config.service.ts";
18
18
  import { formatTurnUsageLog } from "../../shared/turn-usage.ts";
19
19
  import { isAsyncAgentLaunchAck, isTerminalAgentStatus } from "../../shared/background-agent-status.ts";
20
20
  import { cacheReleaseDelayMs, selectWarmIdleEvictions } from "../../services/subprocess-retention.ts";
21
+ import { needsAuthServerNames } from "../../services/mcp-oauth/mcp-oauth-redirect.ts";
22
+ import { mcpStatusEvent, registerMcpSignInSync } from "./chat-mcp-sign-in-sync.ts";
21
23
 
22
24
  /** Resolve the SESSION's provider config — not the global default provider's.
23
25
  * Otherwise a non-default provider's chat (e.g. codex) would inherit claude's values. */
@@ -116,6 +118,8 @@ interface SessionEntry {
116
118
  lastImplicitTeamProbe?: number;
117
119
  /** Compact indicator state — sticky until turn ends or boundary received, synced on reconnect */
118
120
  compactStatus?: "compacting" | null;
121
+ /** MCP servers the subprocess reported as `needs-auth` at init — drives the chat's sign-in bar */
122
+ mcpNeedsAuth?: string[];
119
123
  /** toolUseIds of Bash/Agent calls launched with run_in_background — their spy outlives the tool_result */
120
124
  backgroundToolUseIds?: Set<string>;
121
125
  /** Nested-agent children buffered into turnEvents this turn (see MAX_NESTED_TURN_EVENTS) */
@@ -249,6 +253,28 @@ function broadcastBackgroundRegistry(sessionId: string): void {
249
253
  /** Tracks active sessions — persists even when FE disconnects */
250
254
  const activeSessions = new Map<string, SessionEntry>();
251
255
 
256
+ registerMcpSignInSync({
257
+ sessions: () => activeSessions.entries(),
258
+ broadcast: (sessionId, event) => broadcast(sessionId, event),
259
+ reconnect: async (providerId, sessionId, serverName) => {
260
+ const provider = providerRegistry.get(providerId) as { reconnectMcpServer?: (s: string, n: string) => Promise<string | null> } | undefined;
261
+ return provider?.reconnectMcpServer?.(sessionId, serverName) ?? null;
262
+ },
263
+ // Background agents and shells outlive the turn inside the subprocess; a sign-in elsewhere
264
+ // must never be what kills them.
265
+ canDrop: (sessionId) => {
266
+ const entry = activeSessions.get(sessionId);
267
+ if (!entry || entry.isStreamingActive) return false;
268
+ if ((entry.backgroundToolUseIds?.size ?? 0) > 0) return false;
269
+ return !backgroundShellRegistry.list(sessionId).some((sh) => sh.status !== "stopped");
270
+ },
271
+ dropIdle: (sessionId, serverName) => dropIdleSubprocess(
272
+ sessionId,
273
+ "mcp_sign_in",
274
+ `Subprocess released: it could not see the new ${serverName} sign-in, the next turn starts a fresh one`,
275
+ ),
276
+ });
277
+
252
278
  /** Check if any frontend client is currently connected via WebSocket */
253
279
  export function hasActiveClient(): boolean {
254
280
  for (const entry of activeSessions.values()) {
@@ -609,7 +635,10 @@ async function startSessionConsumer(sessionId: string, providerId: string, conte
609
635
  // System events → transition connecting → thinking, forward compact events
610
636
  if (evType === "system") {
611
637
  const sub = (ev as any).subtype;
612
- if (sub === "compacting") {
638
+ if (sub === "init" && Array.isArray(ev.mcpServers)) {
639
+ entry.mcpNeedsAuth = needsAuthServerNames(ev.mcpServers);
640
+ broadcast(sessionId, mcpStatusEvent(entry.mcpNeedsAuth));
641
+ } else if (sub === "compacting") {
613
642
  entry.compactStatus = "compacting";
614
643
  console.log(`[chat] session=${sessionId} compact_status=compacting (persisted on entry)`);
615
644
  broadcast(sessionId, { type: "compact_status", status: "compacting" });
@@ -1001,6 +1030,7 @@ export const chatWebSocket = {
1001
1030
  pendingApproval: existing.pendingApprovalEvent ?? null,
1002
1031
  sessionTitle: session?.title || null,
1003
1032
  compactStatus: existing.compactStatus ?? null,
1033
+ mcpNeedsAuth: existing.mcpNeedsAuth ?? [],
1004
1034
  model: resolveSessionModel(sessionId),
1005
1035
  effort: resolveSessionEffort(sessionId),
1006
1036
  thinking: resolveSessionThinkingEnabled(sessionId),
@@ -1134,6 +1164,7 @@ export const chatWebSocket = {
1134
1164
  pendingApproval: entry.pendingApprovalEvent ?? null,
1135
1165
  sessionTitle: chatService.getSession(sessionId)?.title || null,
1136
1166
  compactStatus: entry.compactStatus ?? null,
1167
+ mcpNeedsAuth: entry.mcpNeedsAuth ?? [],
1137
1168
  model: resolveSessionModel(sessionId),
1138
1169
  effort: resolveSessionEffort(sessionId),
1139
1170
  thinking: resolveSessionThinkingEnabled(sessionId),
@@ -1342,6 +1373,7 @@ export const chatWebSocket = {
1342
1373
  pendingApproval: entry.pendingApprovalEvent ?? null,
1343
1374
  sessionTitle: chatService.getSession(sessionId)?.title || null,
1344
1375
  compactStatus: entry.compactStatus ?? null,
1376
+ mcpNeedsAuth: entry.mcpNeedsAuth ?? [],
1345
1377
  model: resolveSessionModel(sessionId),
1346
1378
  effort: resolveSessionEffort(sessionId),
1347
1379
  thinking: resolveSessionThinkingEnabled(sessionId),
@@ -1368,6 +1400,7 @@ export const chatWebSocket = {
1368
1400
  pendingApproval: entry.pendingApprovalEvent ?? null,
1369
1401
  sessionTitle: chatService.getSession(sessionId)?.title || null,
1370
1402
  compactStatus: entry.compactStatus ?? null,
1403
+ mcpNeedsAuth: entry.mcpNeedsAuth ?? [],
1371
1404
  model: resolveSessionModel(sessionId),
1372
1405
  effort: resolveSessionEffort(sessionId),
1373
1406
  thinking: resolveSessionThinkingEnabled(sessionId),
@@ -1388,6 +1421,7 @@ export const chatWebSocket = {
1388
1421
  pendingApproval: entry.pendingApprovalEvent ?? null,
1389
1422
  sessionTitle: chatService.getSession(sessionId)?.title || null,
1390
1423
  compactStatus: entry.compactStatus ?? null,
1424
+ mcpNeedsAuth: entry.mcpNeedsAuth ?? [],
1391
1425
  model: resolveSessionModel(sessionId),
1392
1426
  effort: resolveSessionEffort(sessionId),
1393
1427
  thinking: resolveSessionThinkingEnabled(sessionId),
@@ -0,0 +1,58 @@
1
+ import type { McpServerState } from "./mcp-oauth-redirect.ts";
2
+
3
+ /**
4
+ * The control requests PPM sends to a prompt-less Claude subprocess.
5
+ *
6
+ * `mcpAuthenticate` and `mcpSubmitOAuthCallbackUrl` exist on the SDK's `Query` object
7
+ * (they are what Claude's own apps use for "Sign in") but are left out of `sdk.d.ts`,
8
+ * which is why they are typed here rather than imported. A test asserts they still exist
9
+ * so an SDK upgrade that renames them fails loudly instead of the button doing nothing.
10
+ */
11
+ export interface McpControlQuery {
12
+ mcpServerStatus(): Promise<McpServerState[]>;
13
+ mcpAuthenticate(serverName: string, redirectUri?: string): Promise<McpAuthenticateResult>;
14
+ mcpSubmitOAuthCallbackUrl(serverName: string, callbackUrl: string): Promise<unknown>;
15
+ reconnectMcpServer(serverName: string): Promise<void>;
16
+ }
17
+
18
+ /** What the CLI answers to `mcp_authenticate`. */
19
+ export interface McpAuthenticateResult {
20
+ authUrl?: string;
21
+ /** False when the server is already signed in — nothing for the user to do. */
22
+ requiresUserAction: boolean;
23
+ /**
24
+ * False for claude.ai connectors: the grant happens on claude.ai and nothing is ever
25
+ * redirected back, so completion can only be detected by reconnecting.
26
+ */
27
+ callbackExpected: boolean;
28
+ /** "localhost": the CLI's own listener on `callbackPort`. "custom": the redirect URI PPM passed. */
29
+ redirectScheme?: "localhost" | "custom";
30
+ callbackPort?: number;
31
+ state?: string;
32
+ }
33
+
34
+ export interface McpControlHandle {
35
+ query: McpControlQuery;
36
+ close(): void;
37
+ }
38
+
39
+ export type OpenMcpControlQuery = (cwd: string) => Promise<McpControlHandle>;
40
+
41
+ /**
42
+ * A control request that never answers must not hold its subprocess (or everyone waiting
43
+ * on it) forever; this turns silence into an error the caller can settle on.
44
+ */
45
+ export function withTimeout<T>(promise: Promise<T>, ms: number, message: string): Promise<T> {
46
+ let timer: ReturnType<typeof setTimeout> | undefined;
47
+ const timeout = new Promise<never>((_, reject) => { timer = setTimeout(() => reject(new Error(message)), ms); });
48
+ return Promise.race([promise, timeout]).finally(() => clearTimeout(timer));
49
+ }
50
+
51
+ /** Opens control queries through the Claude provider, configured exactly like its chats. */
52
+ export const openClaudeMcpControlQuery: OpenMcpControlQuery = async (cwd) => {
53
+ // Lazy: the registry pulls in every provider, which a unit test of the flow logic must not.
54
+ const { providerRegistry } = await import("../../providers/registry.ts");
55
+ const provider = providerRegistry.get("claude") as { openMcpControlQuery?: (cwd: string) => McpControlHandle } | undefined;
56
+ if (!provider?.openMcpControlQuery) throw new Error("The Claude provider is not available");
57
+ return provider.openMcpControlQuery(cwd);
58
+ };