specrails-desktop 2.30.2 → 2.31.0

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 (462) hide show
  1. package/README.md +28 -24
  2. package/client/dist/assets/{ActivityFeedPage-9DZOvIcY.js → ActivityFeedPage-CAhFRUO4.js} +1 -1
  3. package/client/dist/assets/{AgentBrowserCapture-DqyHbzk7.js → AgentBrowserCapture-Ct4G5lsE.js} +1 -1
  4. package/client/dist/assets/AgentModeCodePane-DQt9WIvu.js +2 -0
  5. package/client/dist/assets/{AgentModeJobsPane-CTIHMUKN.js → AgentModeJobsPane-podaJVl9.js} +1 -1
  6. package/client/dist/assets/AgentsPage-DTZdbW80.js +87 -0
  7. package/client/dist/assets/AnalyticsPage-BskgGPm0.js +1 -0
  8. package/client/dist/assets/CodePage-CvtYo1DU.js +2 -0
  9. package/client/dist/assets/{DesktopAnalyticsPage-CkXUFj3F.js → DesktopAnalyticsPage-Dnf99E8e.js} +1 -1
  10. package/client/dist/assets/{DocsDialog-PhdCJNVj.js → DocsDialog-DHi36rZ4.js} +1 -1
  11. package/client/dist/assets/{DocsPage-BY-wDthx.js → DocsPage-DIdVrQz1.js} +1 -1
  12. package/client/dist/assets/{InteractiveJobComposer-C9I0A4KR.js → InteractiveJobComposer-BHeB2inv.js} +1 -1
  13. package/client/dist/assets/{JobDetailModal-I_RoQJV4.js → JobDetailModal-CF1SKUHH.js} +1 -1
  14. package/client/dist/assets/JobDetailPage-De3rY18h.js +1 -0
  15. package/client/dist/assets/{JobsPage-DcllM4Ip.js → JobsPage-CfAqqJTx.js} +1 -1
  16. package/client/dist/assets/{LoopBuilderPage-DGm_iuff.js → LoopBuilderPage-DSBMuTb3.js} +2 -2
  17. package/client/dist/assets/{LoopPreviewModal-BC-TeFdD.js → LoopPreviewModal-B-c37kA0.js} +1 -1
  18. package/client/dist/assets/LoopsPage-DjnNXdmm.js +1 -0
  19. package/client/dist/assets/{MinimizedChatsContext-Cs34FPTz.js → MinimizedChatsContext-DIsnQMVz.js} +1 -1
  20. package/client/dist/assets/PluginsPage-DA5pQ_6u.js +2 -0
  21. package/client/dist/assets/{ProjectSettingsDialog-nEs-ENrE.js → ProjectSettingsDialog-DA23PSi6.js} +1 -1
  22. package/client/dist/assets/{TemplatePreviewModal-D4qW-J9B.js → TemplatePreviewModal-Cp7IQ-g2.js} +1 -1
  23. package/client/dist/assets/TicketDetailModal-BPD2i_0R.js +14 -0
  24. package/client/dist/assets/{Trans-CPISVji8.js → Trans-cWtExGkC.js} +1 -1
  25. package/client/dist/assets/addspec-4WmLJmBG.js +1 -0
  26. package/client/dist/assets/addspec-BRlk8jON.js +1 -0
  27. package/client/dist/assets/addspec-C7XRejU4.js +1 -0
  28. package/client/dist/assets/addspec-CHW9xf_X.js +1 -0
  29. package/client/dist/assets/addspec-DTmRrWrF.js +1 -0
  30. package/client/dist/assets/addspec-DhmnXER8.js +1 -0
  31. package/client/dist/assets/addspec-Di41DpYV.js +1 -0
  32. package/client/dist/assets/addspec-x3mizDxc.js +1 -0
  33. package/client/dist/assets/agent-BKsuIgIT.js +1 -0
  34. package/client/dist/assets/agent-Bs9NilfI.js +1 -0
  35. package/client/dist/assets/agent-C23h684q.js +1 -0
  36. package/client/dist/assets/agent-CqEjy3Z2.js +1 -0
  37. package/client/dist/assets/agent-D5DZTks1.js +1 -0
  38. package/client/dist/assets/agent-DE8hUYPl.js +1 -0
  39. package/client/dist/assets/agent-DsTuJGSc.js +1 -0
  40. package/client/dist/assets/agent-DwZM05-x.js +1 -0
  41. package/client/dist/assets/agents-45GbINfh.js +1 -0
  42. package/client/dist/assets/agents-BHp0_FQt.js +1 -0
  43. package/client/dist/assets/agents-Bvyi3y6R.js +1 -0
  44. package/client/dist/assets/agents-DMKFStHL.js +1 -0
  45. package/client/dist/assets/agents-DaYcxAr1.js +1 -0
  46. package/client/dist/assets/agents-IUOgqT5t.js +1 -0
  47. package/client/dist/assets/agents-ms7TwG16.js +1 -0
  48. package/client/dist/assets/agents-rzPnqoBR.js +1 -0
  49. package/client/dist/assets/agentstudio-8TQqBenW.js +1 -0
  50. package/client/dist/assets/agentstudio-B8Z_mmEX.js +1 -0
  51. package/client/dist/assets/agentstudio-BZR9YmLs.js +1 -0
  52. package/client/dist/assets/agentstudio-Bge9pAWb.js +1 -0
  53. package/client/dist/assets/agentstudio-Bz169Vzt.js +1 -0
  54. package/client/dist/assets/agentstudio-D6L2jpNi.js +1 -0
  55. package/client/dist/assets/agentstudio-K2-Z7CL0.js +1 -0
  56. package/client/dist/assets/agentstudio-Zmgzthv8.js +1 -0
  57. package/client/dist/assets/analytics-B8Qtptbl.js +1 -0
  58. package/client/dist/assets/analytics-BHqUazhP.js +1 -0
  59. package/client/dist/assets/analytics-BPptRY4f.js +1 -0
  60. package/client/dist/assets/analytics-BxDGOIwf.js +1 -0
  61. package/client/dist/assets/analytics-C9RaQcE3.js +1 -0
  62. package/client/dist/assets/analytics-CJIwqrEl.js +1 -0
  63. package/client/dist/assets/analytics-D_OZdQ9O.js +1 -0
  64. package/client/dist/assets/analytics-We6lUMI8.js +1 -0
  65. package/client/dist/assets/builder-3bygQtZi.js +1 -0
  66. package/client/dist/assets/builder-BiUG9Z5K.js +1 -0
  67. package/client/dist/assets/builder-CGRz_-zP.js +1 -0
  68. package/client/dist/assets/builder-CWYPw7Cw.js +1 -0
  69. package/client/dist/assets/builder-CX1437hF.js +1 -0
  70. package/client/dist/assets/{builder-CwA4T7gP.js → builder-Cowk9jtL.js} +1 -1
  71. package/client/dist/assets/builder-DwDexYRE.js +1 -0
  72. package/client/dist/assets/builder-skfnZ1uC.js +1 -0
  73. package/client/dist/assets/code-B0O8-p8k.js +1 -0
  74. package/client/dist/assets/code-B1VLZDhS.js +1 -0
  75. package/client/dist/assets/code-B86IMAfz.js +1 -0
  76. package/client/dist/assets/code-Bpj4Ddml.js +1 -0
  77. package/client/dist/assets/code-BykBZ5-e.js +1 -0
  78. package/client/dist/assets/code-CHPkOkDS.js +1 -0
  79. package/client/dist/assets/code-CkUuW4m9.js +1 -0
  80. package/client/dist/assets/code-cAqaqMTx.js +1 -0
  81. package/client/dist/assets/{dashboard-Dy2Um-US.js → dashboard-BVq6kRJe.js} +1 -1
  82. package/client/dist/assets/{dashboard-CHCEkds4.js → dashboard-BaKPPmLH.js} +1 -1
  83. package/client/dist/assets/{dashboard-CrlTV1ms.js → dashboard-Bm8e4zYG.js} +1 -1
  84. package/client/dist/assets/{dashboard-WhzOQw1S.js → dashboard-Bt6IesnM.js} +1 -1
  85. package/client/dist/assets/{dashboard-Cslu_GCH.js → dashboard-C_NQZle_.js} +1 -1
  86. package/client/dist/assets/{dashboard-DweT3Y4R.js → dashboard-CyZKoGoI.js} +1 -1
  87. package/client/dist/assets/{dashboard-DUU8-_Rg.js → dashboard-HIUdUBQ0.js} +1 -1
  88. package/client/dist/assets/{dashboard-Cqmmbh3l.js → dashboard-pm4Mr229.js} +1 -1
  89. package/client/dist/assets/{dist-js-jwoXo0YD.js → dist-js-BaP-xLOy.js} +1 -1
  90. package/client/dist/assets/{dist-js-Dj72W6qo.js → dist-js-CLlgOa5a.js} +1 -1
  91. package/client/dist/assets/format-command-C0D9IPmV.js +1 -0
  92. package/client/dist/assets/{formatDistanceToNow-ByKli7Q_.js → formatDistanceToNow-DUyzS4rr.js} +1 -1
  93. package/client/dist/assets/{getTimezoneOffsetInMilliseconds-qrcBaLW1.js → getTimezoneOffsetInMilliseconds-BCIwmmTj.js} +1 -1
  94. package/client/dist/assets/{i18n-Bf9Ckd10.js → i18n-D21Pr8Sj.js} +2 -2
  95. package/client/dist/assets/index-BdSDUFnA.css +2 -0
  96. package/client/dist/assets/index-Ci4-JKDr.js +77 -0
  97. package/client/dist/assets/{jobs-CVFD8rar.js → jobs-BAls3bWq.js} +1 -1
  98. package/client/dist/assets/{jobs-DoSaFzAa.js → jobs-B_TeSQxb.js} +1 -1
  99. package/client/dist/assets/jobs-Bgq-PZkC.js +1 -0
  100. package/client/dist/assets/jobs-Bpks6doU.js +1 -0
  101. package/client/dist/assets/jobs-CN_zv4Gr.js +1 -0
  102. package/client/dist/assets/jobs-DUlWSYq2.js +1 -0
  103. package/client/dist/assets/{jobs-B-sDHEay.js → jobs-DhxKgWFq.js} +1 -1
  104. package/client/dist/assets/{jobs-lk1_Dq1U.js → jobs-SwYS2M-B.js} +1 -1
  105. package/client/dist/assets/nav-BHDiSIVu.js +1 -0
  106. package/client/dist/assets/nav-CMiYAq4i.js +1 -0
  107. package/client/dist/assets/nav-CqS7NrDW.js +1 -0
  108. package/client/dist/assets/nav-D-0M7xfv.js +1 -0
  109. package/client/dist/assets/nav-D65O6u9I.js +1 -0
  110. package/client/dist/assets/nav-D_LKYvBr.js +1 -0
  111. package/client/dist/assets/nav-FDTJmU79.js +1 -0
  112. package/client/dist/assets/nav-VaB-C4k5.js +1 -0
  113. package/client/dist/assets/provider-capabilities-BvoExILy.js +1 -0
  114. package/client/dist/assets/settings-BveOJoaI.js +1 -0
  115. package/client/dist/assets/settings-Cj1C6F5k.js +1 -0
  116. package/client/dist/assets/settings-CuVwOpe0.js +1 -0
  117. package/client/dist/assets/settings-D5X4DfRc.js +1 -0
  118. package/client/dist/assets/settings-DXwxe-BW.js +1 -0
  119. package/client/dist/assets/settings-Di81Ba_V.js +1 -0
  120. package/client/dist/assets/settings-jbLjCYC_.js +1 -0
  121. package/client/dist/assets/settings-wIZNPFnn.js +1 -0
  122. package/client/dist/assets/setup-BBiHEO7h.js +1 -0
  123. package/client/dist/assets/setup-BlTWhNIQ.js +1 -0
  124. package/client/dist/assets/setup-Bp2L_oiP.js +1 -0
  125. package/client/dist/assets/setup-CInYXp1-.js +1 -0
  126. package/client/dist/assets/{setup-CITtPjed.js → setup-CYH3bP6K.js} +1 -1
  127. package/client/dist/assets/setup-DwcQ921P.js +1 -0
  128. package/client/dist/assets/setup-PBLjuDSs.js +1 -0
  129. package/client/dist/assets/setup-bx6FtoBp.js +1 -0
  130. package/client/dist/assets/{spending-DMnCXy84.js → spending-BCyUxoOI.js} +1 -1
  131. package/client/dist/assets/{tickets-B8BuAaj5.js → tickets-BL9r1G0e.js} +1 -1
  132. package/client/dist/assets/{tickets-DpmdE8tv.js → tickets-B_NyHJ8f.js} +1 -1
  133. package/client/dist/assets/{tickets-B_yh4qQk.js → tickets-BarF1wuG.js} +1 -1
  134. package/client/dist/assets/{tickets-pAUJkDTu.js → tickets-CclVhpQA.js} +1 -1
  135. package/client/dist/assets/{tickets-D_yDquEw.js → tickets-CqqOHbli.js} +1 -1
  136. package/client/dist/assets/{tickets-CFk2n2Or.js → tickets-DCkFSiqt.js} +1 -1
  137. package/client/dist/assets/{tickets-DQp7JS2R.js → tickets-Djn3ccfT.js} +1 -1
  138. package/client/dist/assets/{tickets-Bs37scwY.js → tickets-MiHBPsy_.js} +1 -1
  139. package/client/dist/assets/{useBrowserCaptureSession-CkBJShUw.js → useBrowserCaptureSession-CkVCDzer.js} +1 -1
  140. package/client/dist/assets/{useDesktop-B4pSBIDd.js → useDesktop-DCP_MHp9.js} +1 -1
  141. package/client/dist/index.html +25 -24
  142. package/docs/README.md +7 -3
  143. package/docs/cli.md +3 -3
  144. package/docs/codex.md +51 -64
  145. package/docs/creating-specs.md +36 -4
  146. package/docs/customizing.md +5 -5
  147. package/docs/gemini.md +35 -47
  148. package/docs/getting-started.md +15 -9
  149. package/docs/guide/de/agents/2-profiles-and-the-balanced-default.md +5 -1
  150. package/docs/guide/de/agents/4-custom-agents-catalog.md +7 -4
  151. package/docs/guide/de/getting-started/1-what-is-specrails.md +1 -0
  152. package/docs/guide/de/getting-started/2-installing-and-first-run.md +1 -0
  153. package/docs/guide/de/getting-started/4-the-dashboard-tour.md +4 -4
  154. package/docs/guide/de/integrations/1-ai-providers.md +29 -18
  155. package/docs/guide/de/integrations/2-plugins.md +3 -3
  156. package/docs/guide/de/integrations/6-agent-chat.md +8 -1
  157. package/docs/guide/de/pipeline/1-rails-and-jobs.md +9 -6
  158. package/docs/guide/de/pipeline/2-the-job-detail-view.md +3 -1
  159. package/docs/guide/de/pipeline/3-batch-implement-and-multi-feature.md +1 -1
  160. package/docs/guide/de/pipeline/4-picking-an-engine-per-rail.md +13 -13
  161. package/docs/guide/de/pipeline/5-the-loop-builder.md +6 -2
  162. package/docs/guide/de/specs/2-add-spec-quick-mode.md +6 -1
  163. package/docs/guide/en/agents/2-profiles-and-the-balanced-default.md +7 -1
  164. package/docs/guide/en/agents/4-custom-agents-catalog.md +17 -4
  165. package/docs/guide/en/getting-started/1-what-is-specrails.md +2 -1
  166. package/docs/guide/en/getting-started/2-installing-and-first-run.md +2 -1
  167. package/docs/guide/en/getting-started/4-the-dashboard-tour.md +5 -5
  168. package/docs/guide/en/insights/3-code-explorer.md +5 -0
  169. package/docs/guide/en/integrations/1-ai-providers.md +63 -22
  170. package/docs/guide/en/integrations/2-plugins.md +3 -3
  171. package/docs/guide/en/integrations/6-agent-chat.md +14 -2
  172. package/docs/guide/en/pipeline/1-rails-and-jobs.md +19 -7
  173. package/docs/guide/en/pipeline/2-the-job-detail-view.md +3 -1
  174. package/docs/guide/en/pipeline/3-batch-implement-and-multi-feature.md +1 -1
  175. package/docs/guide/en/pipeline/4-picking-an-engine-per-rail.md +25 -11
  176. package/docs/guide/en/pipeline/5-the-loop-builder.md +7 -2
  177. package/docs/guide/en/specs/2-add-spec-quick-mode.md +10 -1
  178. package/docs/guide/es/agents/2-profiles-and-the-balanced-default.md +5 -1
  179. package/docs/guide/es/agents/4-custom-agents-catalog.md +7 -4
  180. package/docs/guide/es/getting-started/1-what-is-specrails.md +1 -0
  181. package/docs/guide/es/getting-started/2-installing-and-first-run.md +1 -0
  182. package/docs/guide/es/getting-started/4-the-dashboard-tour.md +4 -4
  183. package/docs/guide/es/integrations/1-ai-providers.md +30 -18
  184. package/docs/guide/es/integrations/2-plugins.md +3 -3
  185. package/docs/guide/es/integrations/6-agent-chat.md +8 -1
  186. package/docs/guide/es/pipeline/1-rails-and-jobs.md +9 -6
  187. package/docs/guide/es/pipeline/2-the-job-detail-view.md +3 -1
  188. package/docs/guide/es/pipeline/3-batch-implement-and-multi-feature.md +1 -1
  189. package/docs/guide/es/pipeline/4-picking-an-engine-per-rail.md +15 -13
  190. package/docs/guide/es/pipeline/5-the-loop-builder.md +6 -2
  191. package/docs/guide/es/specs/2-add-spec-quick-mode.md +6 -1
  192. package/docs/guide/fr/agents/2-profiles-and-the-balanced-default.md +5 -1
  193. package/docs/guide/fr/agents/4-custom-agents-catalog.md +7 -4
  194. package/docs/guide/fr/getting-started/1-what-is-specrails.md +1 -0
  195. package/docs/guide/fr/getting-started/2-installing-and-first-run.md +1 -0
  196. package/docs/guide/fr/getting-started/4-the-dashboard-tour.md +4 -4
  197. package/docs/guide/fr/integrations/1-ai-providers.md +29 -18
  198. package/docs/guide/fr/integrations/2-plugins.md +3 -3
  199. package/docs/guide/fr/integrations/6-agent-chat.md +8 -1
  200. package/docs/guide/fr/pipeline/1-rails-and-jobs.md +9 -6
  201. package/docs/guide/fr/pipeline/2-the-job-detail-view.md +3 -1
  202. package/docs/guide/fr/pipeline/3-batch-implement-and-multi-feature.md +1 -1
  203. package/docs/guide/fr/pipeline/4-picking-an-engine-per-rail.md +15 -13
  204. package/docs/guide/fr/pipeline/5-the-loop-builder.md +6 -2
  205. package/docs/guide/fr/specs/2-add-spec-quick-mode.md +6 -1
  206. package/docs/guide/it/agents/2-profiles-and-the-balanced-default.md +5 -1
  207. package/docs/guide/it/agents/4-custom-agents-catalog.md +7 -4
  208. package/docs/guide/it/getting-started/1-what-is-specrails.md +1 -0
  209. package/docs/guide/it/getting-started/2-installing-and-first-run.md +1 -0
  210. package/docs/guide/it/getting-started/4-the-dashboard-tour.md +4 -4
  211. package/docs/guide/it/integrations/1-ai-providers.md +28 -18
  212. package/docs/guide/it/integrations/2-plugins.md +3 -3
  213. package/docs/guide/it/integrations/6-agent-chat.md +8 -1
  214. package/docs/guide/it/pipeline/1-rails-and-jobs.md +9 -6
  215. package/docs/guide/it/pipeline/2-the-job-detail-view.md +3 -1
  216. package/docs/guide/it/pipeline/3-batch-implement-and-multi-feature.md +1 -1
  217. package/docs/guide/it/pipeline/4-picking-an-engine-per-rail.md +13 -13
  218. package/docs/guide/it/pipeline/5-the-loop-builder.md +6 -2
  219. package/docs/guide/it/specs/2-add-spec-quick-mode.md +6 -1
  220. package/docs/guide/ja/agents/2-profiles-and-the-balanced-default.md +6 -1
  221. package/docs/guide/ja/agents/4-custom-agents-catalog.md +7 -4
  222. package/docs/guide/ja/getting-started/1-what-is-specrails.md +1 -0
  223. package/docs/guide/ja/getting-started/2-installing-and-first-run.md +1 -0
  224. package/docs/guide/ja/getting-started/4-the-dashboard-tour.md +4 -4
  225. package/docs/guide/ja/integrations/1-ai-providers.md +28 -18
  226. package/docs/guide/ja/integrations/2-plugins.md +3 -3
  227. package/docs/guide/ja/integrations/6-agent-chat.md +7 -1
  228. package/docs/guide/ja/pipeline/1-rails-and-jobs.md +9 -6
  229. package/docs/guide/ja/pipeline/2-the-job-detail-view.md +3 -1
  230. package/docs/guide/ja/pipeline/3-batch-implement-and-multi-feature.md +1 -1
  231. package/docs/guide/ja/pipeline/4-picking-an-engine-per-rail.md +14 -13
  232. package/docs/guide/ja/pipeline/5-the-loop-builder.md +6 -2
  233. package/docs/guide/ja/specs/2-add-spec-quick-mode.md +6 -1
  234. package/docs/guide/pt/agents/2-profiles-and-the-balanced-default.md +5 -1
  235. package/docs/guide/pt/agents/4-custom-agents-catalog.md +7 -4
  236. package/docs/guide/pt/getting-started/1-what-is-specrails.md +1 -0
  237. package/docs/guide/pt/getting-started/2-installing-and-first-run.md +1 -0
  238. package/docs/guide/pt/getting-started/4-the-dashboard-tour.md +4 -4
  239. package/docs/guide/pt/integrations/1-ai-providers.md +28 -18
  240. package/docs/guide/pt/integrations/2-plugins.md +3 -3
  241. package/docs/guide/pt/integrations/6-agent-chat.md +7 -1
  242. package/docs/guide/pt/pipeline/1-rails-and-jobs.md +9 -6
  243. package/docs/guide/pt/pipeline/2-the-job-detail-view.md +2 -1
  244. package/docs/guide/pt/pipeline/3-batch-implement-and-multi-feature.md +1 -1
  245. package/docs/guide/pt/pipeline/4-picking-an-engine-per-rail.md +14 -13
  246. package/docs/guide/pt/pipeline/5-the-loop-builder.md +6 -2
  247. package/docs/guide/pt/specs/2-add-spec-quick-mode.md +6 -1
  248. package/docs/guide/zh/agents/2-profiles-and-the-balanced-default.md +4 -1
  249. package/docs/guide/zh/agents/4-custom-agents-catalog.md +7 -4
  250. package/docs/guide/zh/getting-started/1-what-is-specrails.md +1 -0
  251. package/docs/guide/zh/getting-started/2-installing-and-first-run.md +1 -0
  252. package/docs/guide/zh/getting-started/4-the-dashboard-tour.md +4 -4
  253. package/docs/guide/zh/integrations/1-ai-providers.md +26 -18
  254. package/docs/guide/zh/integrations/2-plugins.md +3 -3
  255. package/docs/guide/zh/integrations/6-agent-chat.md +6 -1
  256. package/docs/guide/zh/pipeline/1-rails-and-jobs.md +9 -6
  257. package/docs/guide/zh/pipeline/2-the-job-detail-view.md +2 -1
  258. package/docs/guide/zh/pipeline/3-batch-implement-and-multi-feature.md +1 -1
  259. package/docs/guide/zh/pipeline/4-picking-an-engine-per-rail.md +12 -12
  260. package/docs/guide/zh/pipeline/5-the-loop-builder.md +6 -2
  261. package/docs/guide/zh/specs/2-add-spec-quick-mode.md +6 -1
  262. package/docs/internals/README.md +4 -1
  263. package/docs/internals/adding-a-provider.md +32 -28
  264. package/docs/internals/api-reference.md +37 -13
  265. package/docs/internals/architecture.md +5 -5
  266. package/docs/internals/bundled-framework-build-plan.md +3 -3
  267. package/docs/internals/code-explorer-story.md +4 -0
  268. package/docs/internals/configuration.md +17 -5
  269. package/docs/internals/global-artifacts-alignment-contract.md +1 -1
  270. package/docs/internals/operations-runbook.md +1 -1
  271. package/docs/internals/profiles.md +37 -45
  272. package/docs/internals/project-builder.md +16 -0
  273. package/docs/kimi.md +291 -0
  274. package/docs/mcp.md +9 -2
  275. package/docs/platforms/macos.md +17 -7
  276. package/docs/platforms/windows.md +9 -4
  277. package/docs/running-pipelines.md +46 -12
  278. package/docs/terminal.md +1 -1
  279. package/docs/tracking-cost.md +25 -6
  280. package/package.json +4 -2
  281. package/server/dist/agent-chat-manager.js +74 -32
  282. package/server/dist/agent-chat-router.js +40 -17
  283. package/server/dist/agent-cwd-manager.js +14 -7
  284. package/server/dist/agent-generator.js +69 -85
  285. package/server/dist/agent-mcp-config.js +17 -11
  286. package/server/dist/agent-operator-prompt.js +29 -20
  287. package/server/dist/agent-refine-manager.js +58 -38
  288. package/server/dist/ai-invocations.js +65 -20
  289. package/server/dist/attachment-manager.js +49 -15
  290. package/server/dist/blueprint-chat-manager.js +41 -16
  291. package/server/dist/blueprint-router.js +40 -6
  292. package/server/dist/chat-manager.js +165 -89
  293. package/server/dist/code-explorer-router.js +26 -7
  294. package/server/dist/contract-refine-runner.js +146 -113
  295. package/server/dist/core-compat.js +57 -2
  296. package/server/dist/core-package.js +3 -3
  297. package/server/dist/core-update-manager.js +17 -1
  298. package/server/dist/db.js +60 -5
  299. package/server/dist/desktop-analytics.js +44 -6
  300. package/server/dist/desktop-router.js +127 -11
  301. package/server/dist/explore-cwd-manager.js +19 -3
  302. package/server/dist/feature-flags.js +1 -1
  303. package/server/dist/file-story-manager.js +3 -3
  304. package/server/dist/file-summary-generator.js +21 -15
  305. package/server/dist/file-summary-manager.js +6 -6
  306. package/server/dist/framework-manager.js +19 -2
  307. package/server/dist/framework-migration.js +27 -12
  308. package/server/dist/install-config-path.js +1 -1
  309. package/server/dist/interactive-job-session.js +8 -6
  310. package/server/dist/loop-command-catalog.js +27 -10
  311. package/server/dist/loop-executors.js +59 -27
  312. package/server/dist/loop-factory.js +2 -2
  313. package/server/dist/loop-run-manager.js +100 -39
  314. package/server/dist/loops-router.js +1 -1
  315. package/server/dist/mcp/guide.js +23 -16
  316. package/server/dist/mcp/tools/agents.js +47 -41
  317. package/server/dist/mcp/tools/analytics.js +2 -2
  318. package/server/dist/mcp/tools/loops.js +2 -2
  319. package/server/dist/mcp/tools/plugins.js +14 -9
  320. package/server/dist/mcp/tools/rails.js +12 -13
  321. package/server/dist/mcp/tools/support.js +1 -1
  322. package/server/dist/mobile/mobile-router.js +5 -1
  323. package/server/dist/offline-assemble.js +13 -1
  324. package/server/dist/path-resolver.js +2 -2
  325. package/server/dist/plugin-manager.js +391 -106
  326. package/server/dist/plugins/claude-md-mutation.js +5 -4
  327. package/server/dist/plugins/drift.js +32 -29
  328. package/server/dist/plugins/rail-integration.js +13 -2
  329. package/server/dist/plugins/serena/install.js +25 -17
  330. package/server/dist/plugins/serena/manifest.js +22 -5
  331. package/server/dist/plugins/serena/verify.js +46 -16
  332. package/server/dist/plugins-router.js +59 -8
  333. package/server/dist/profile-manager.js +148 -36
  334. package/server/dist/profiles-router.js +311 -82
  335. package/server/dist/project-registry.js +2 -2
  336. package/server/dist/project-router-chat.js +17 -8
  337. package/server/dist/project-router-helpers.js +93 -5
  338. package/server/dist/project-router-jobs.js +76 -12
  339. package/server/dist/project-router-loop-runs.js +42 -14
  340. package/server/dist/project-router-settings.js +31 -10
  341. package/server/dist/project-router-setup.js +4 -5
  342. package/server/dist/project-router-spending.js +15 -3
  343. package/server/dist/project-router-tickets.js +187 -181
  344. package/server/dist/project-router.js +1 -0
  345. package/server/dist/proposal-manager.js +125 -85
  346. package/server/dist/providers/claude-adapter.js +14 -0
  347. package/server/dist/providers/codex-adapter.js +20 -0
  348. package/server/dist/providers/gemini-adapter.js +14 -0
  349. package/server/dist/providers/index.js +18 -1
  350. package/server/dist/providers/kimi-adapter.js +361 -0
  351. package/server/dist/providers/kimi-skill-prompt.js +332 -0
  352. package/server/dist/providers/runtime.js +137 -0
  353. package/server/dist/queue-manager.js +103 -78
  354. package/server/dist/rail-isolated-launch.js +1 -1
  355. package/server/dist/rails-router.js +51 -38
  356. package/server/dist/result-event.js +5 -0
  357. package/server/dist/schemas/profile.v1.json +3 -4
  358. package/server/dist/setup-manager.js +222 -101
  359. package/server/dist/setup-prerequisites.js +7 -1
  360. package/server/dist/smash-runner.js +104 -57
  361. package/server/dist/spawn-lifecycle.js +18 -14
  362. package/server/dist/spec-launcher-manager.js +66 -54
  363. package/server/dist/spec-models.js +15 -6
  364. package/server/dist/spending.js +110 -17
  365. package/server/dist/util/cli-prompt.js +142 -5
  366. package/server/dist/util/stream-display.js +46 -0
  367. package/server/dist/workspace-manager.js +100 -37
  368. package/server/dist/worktree-manager.js +4 -0
  369. package/server/dist/worktree-overlay.js +12 -3
  370. package/client/dist/assets/AgentModeCodePane-BAYyFRmY.js +0 -2
  371. package/client/dist/assets/AgentsPage-DuyJuaFs.js +0 -86
  372. package/client/dist/assets/AnalyticsPage-BY7gtCx6.js +0 -1
  373. package/client/dist/assets/CodePage-vyQr40Vl.js +0 -2
  374. package/client/dist/assets/JobDetailPage-CU4FEnUC.js +0 -1
  375. package/client/dist/assets/LoopsPage-SqglnXlp.js +0 -1
  376. package/client/dist/assets/PluginsPage-B89S8o1a.js +0 -2
  377. package/client/dist/assets/TicketDetailModal-CI47Dq9E.js +0 -14
  378. package/client/dist/assets/addspec-BXjvkh5q.js +0 -1
  379. package/client/dist/assets/addspec-C4bwrmpi.js +0 -1
  380. package/client/dist/assets/addspec-CArHXUDM.js +0 -1
  381. package/client/dist/assets/addspec-DTKJcXiR.js +0 -1
  382. package/client/dist/assets/addspec-DbNBbrha.js +0 -1
  383. package/client/dist/assets/addspec-Rw8uVBC1.js +0 -1
  384. package/client/dist/assets/addspec-htHGZHOe.js +0 -1
  385. package/client/dist/assets/addspec-lr_LP6jk.js +0 -1
  386. package/client/dist/assets/agent-BABjRt0P.js +0 -1
  387. package/client/dist/assets/agent-BuUpgqVx.js +0 -1
  388. package/client/dist/assets/agent-CCDZesgU.js +0 -1
  389. package/client/dist/assets/agent-CVn64zWH.js +0 -1
  390. package/client/dist/assets/agent-CWeOny0E.js +0 -1
  391. package/client/dist/assets/agent-DOysmad_.js +0 -1
  392. package/client/dist/assets/agent-DSsjTBhn.js +0 -1
  393. package/client/dist/assets/agent-DlZMxfM9.js +0 -1
  394. package/client/dist/assets/agents-6ZOJXWVk.js +0 -1
  395. package/client/dist/assets/agents-BZ0XQKjg.js +0 -1
  396. package/client/dist/assets/agents-BqWklnHR.js +0 -1
  397. package/client/dist/assets/agents-Bz-thHkB.js +0 -1
  398. package/client/dist/assets/agents-CJclElx6.js +0 -1
  399. package/client/dist/assets/agents-D_l9L0VA.js +0 -1
  400. package/client/dist/assets/agents-DhG7mg6-.js +0 -1
  401. package/client/dist/assets/agents-u_C0jPB5.js +0 -1
  402. package/client/dist/assets/agentstudio-BF19hbXj.js +0 -1
  403. package/client/dist/assets/agentstudio-BIUe1Im2.js +0 -1
  404. package/client/dist/assets/agentstudio-BLGvxWBx.js +0 -1
  405. package/client/dist/assets/agentstudio-D9UKiWTj.js +0 -1
  406. package/client/dist/assets/agentstudio-Drs37nIM.js +0 -1
  407. package/client/dist/assets/agentstudio-nEnKkcXM.js +0 -1
  408. package/client/dist/assets/agentstudio-pyFcSBKy.js +0 -1
  409. package/client/dist/assets/agentstudio-zfTzQh3U.js +0 -1
  410. package/client/dist/assets/analytics-BC7mwR4s.js +0 -1
  411. package/client/dist/assets/analytics-BiNg8edP.js +0 -1
  412. package/client/dist/assets/analytics-C0s_T2GN.js +0 -1
  413. package/client/dist/assets/analytics-CdsAPAwZ.js +0 -1
  414. package/client/dist/assets/analytics-Cr1_q-lC.js +0 -1
  415. package/client/dist/assets/analytics-DZ8Aoq3I.js +0 -1
  416. package/client/dist/assets/analytics-IEsKZvpW.js +0 -1
  417. package/client/dist/assets/analytics-SRPf84OT.js +0 -1
  418. package/client/dist/assets/builder-BH8Fua-g.js +0 -1
  419. package/client/dist/assets/builder-BoV7DgKd.js +0 -1
  420. package/client/dist/assets/builder-Cc_Lf79C.js +0 -1
  421. package/client/dist/assets/builder-CwZ7Bme2.js +0 -1
  422. package/client/dist/assets/builder-CxW-JFsR.js +0 -1
  423. package/client/dist/assets/builder-D94lrV7x.js +0 -1
  424. package/client/dist/assets/builder-cqHpCyGa.js +0 -1
  425. package/client/dist/assets/code-76gS0IdL.js +0 -1
  426. package/client/dist/assets/code-BDXEM4ml.js +0 -1
  427. package/client/dist/assets/code-C1VwxlML.js +0 -1
  428. package/client/dist/assets/code-CnMZyJpr.js +0 -1
  429. package/client/dist/assets/code-D20ef9Cf.js +0 -1
  430. package/client/dist/assets/code-DdOXaWRY.js +0 -1
  431. package/client/dist/assets/code-Duohuf9l.js +0 -1
  432. package/client/dist/assets/code-geHmknB9.js +0 -1
  433. package/client/dist/assets/format-command-Dfa1o2d-.js +0 -1
  434. package/client/dist/assets/index-Dh9i4CnN.css +0 -2
  435. package/client/dist/assets/index-F9r8mWP_.js +0 -76
  436. package/client/dist/assets/jobs-BCtGfgUA.js +0 -1
  437. package/client/dist/assets/jobs-BHR_EnDI.js +0 -1
  438. package/client/dist/assets/jobs-BMbZJjJJ.js +0 -1
  439. package/client/dist/assets/jobs-DzSIR8V6.js +0 -1
  440. package/client/dist/assets/nav-BRJBxaGt.js +0 -1
  441. package/client/dist/assets/nav-CAGKv3_F.js +0 -1
  442. package/client/dist/assets/nav-CNskRJKQ.js +0 -1
  443. package/client/dist/assets/nav-DY93irBL.js +0 -1
  444. package/client/dist/assets/nav-DhjvYgqt.js +0 -1
  445. package/client/dist/assets/nav-Gb-r4wop.js +0 -1
  446. package/client/dist/assets/nav-TvumAFD2.js +0 -1
  447. package/client/dist/assets/nav-myFaXRHD.js +0 -1
  448. package/client/dist/assets/settings-B0ypw9uw.js +0 -1
  449. package/client/dist/assets/settings-B9ilOSYO.js +0 -1
  450. package/client/dist/assets/settings-BqlWHRHA.js +0 -1
  451. package/client/dist/assets/settings-D8dlqt4d.js +0 -1
  452. package/client/dist/assets/settings-DAZqjX2a.js +0 -1
  453. package/client/dist/assets/settings-DRh39v1S.js +0 -1
  454. package/client/dist/assets/settings-Mj1OJbfd.js +0 -1
  455. package/client/dist/assets/settings-lRReaSYm.js +0 -1
  456. package/client/dist/assets/setup-3832BJCo.js +0 -1
  457. package/client/dist/assets/setup-B8J-gECe.js +0 -1
  458. package/client/dist/assets/setup-BCIOxVsc.js +0 -1
  459. package/client/dist/assets/setup-BciLj_tU.js +0 -1
  460. package/client/dist/assets/setup-CTkE3cfh.js +0 -1
  461. package/client/dist/assets/setup-ajB4rYm-.js +0 -1
  462. package/client/dist/assets/setup-xFO1wNl0.js +0 -1
@@ -11,11 +11,18 @@ const claude_approval_1 = require("./plugins/claude-approval");
11
11
  const drift_1 = require("./plugins/drift");
12
12
  const providers_1 = require("./providers");
13
13
  const paths_1 = require("./plugins/paths");
14
- function readMcpServersMap(projectPath) {
14
+ function providerMcpJsonPath(projectPath, providerId) {
15
+ if (providerId && (0, providers_1.hasAdapter)(providerId)) {
16
+ return (0, providers_1.getAdapter)(providerId).projectMcpPath?.(projectPath) ?? (0, paths_1.mcpJsonPath)(projectPath);
17
+ }
18
+ return (0, paths_1.mcpJsonPath)(projectPath);
19
+ }
20
+ function readMcpServersMap(projectPath, providerId) {
15
21
  try {
16
- if (!fs_1.default.existsSync((0, paths_1.mcpJsonPath)(projectPath)))
22
+ const file = providerMcpJsonPath(projectPath, providerId);
23
+ if (!fs_1.default.existsSync(file))
17
24
  return {};
18
- const raw = fs_1.default.readFileSync((0, paths_1.mcpJsonPath)(projectPath), 'utf8');
25
+ const raw = fs_1.default.readFileSync(file, 'utf8');
19
26
  if (!raw.trim())
20
27
  return {};
21
28
  const parsed = JSON.parse(raw);
@@ -27,6 +34,87 @@ function readMcpServersMap(projectPath) {
27
34
  }
28
35
  const json_mutation_1 = require("./plugins/json-mutation");
29
36
  const contributors_1 = require("./plugins/contributors");
37
+ const LEGACY_PROVIDER = 'claude';
38
+ function targetProvider(providerId) {
39
+ return providerId?.trim() || LEGACY_PROVIDER;
40
+ }
41
+ function legacyProvider(providerId, legacyProviderId) {
42
+ return legacyProviderId?.trim() || providerId;
43
+ }
44
+ function legacyProviderState(entry) {
45
+ return {
46
+ installedAt: entry.installedAt,
47
+ installedFiles: [...(entry.installedFiles ?? [])],
48
+ health: entry.health,
49
+ healthReason: entry.healthReason,
50
+ ownedMcpServers: [...(entry.ownedMcpServers ?? [])],
51
+ };
52
+ }
53
+ /** Resolve one provider without pretending a legacy primary install also
54
+ * belongs to every secondary provider. */
55
+ function getProviderState(entry, providerId, legacyProviderId) {
56
+ if (!entry)
57
+ return undefined;
58
+ if (entry.providers)
59
+ return entry.providers[providerId];
60
+ return providerId === legacyProviderId ? legacyProviderState(entry) : undefined;
61
+ }
62
+ /** Materialize the provider map lazily when the first post-migration mutation
63
+ * occurs. Until then legacy state remains byte-compatible on disk. */
64
+ function ensureProviderStates(entry, legacyProviderId) {
65
+ if (!entry.providers) {
66
+ entry.providers = { [legacyProviderId]: legacyProviderState(entry) };
67
+ }
68
+ return entry.providers;
69
+ }
70
+ /** Keep the schema-v1 aggregate fields truthful for older Desktop builds and
71
+ * diagnostics that have not learned the provider map yet. */
72
+ function syncAggregateState(entry) {
73
+ const states = Object.values(entry.providers ?? {});
74
+ if (states.length === 0)
75
+ return;
76
+ entry.installedAt = states
77
+ .map((state) => state.installedAt)
78
+ .sort()[0] ?? entry.installedAt;
79
+ entry.installedFiles = Array.from(new Set(states.flatMap((state) => state.installedFiles ?? [])));
80
+ const degraded = states.find((state) => state.health === 'degraded');
81
+ if (degraded) {
82
+ entry.health = 'degraded';
83
+ entry.healthReason = degraded.healthReason;
84
+ }
85
+ else if (states.every((state) => state.health === 'ok')) {
86
+ entry.health = 'ok';
87
+ delete entry.healthReason;
88
+ }
89
+ else {
90
+ entry.health = 'unknown';
91
+ delete entry.healthReason;
92
+ }
93
+ entry.ownedMcpServers = Array.from(new Set(states.flatMap((state) => state.ownedMcpServers ?? [])));
94
+ }
95
+ function supportsProvider(plugin, providerId) {
96
+ const declared = plugin.manifest.providerSupport;
97
+ return declared === undefined
98
+ ? providerId === LEGACY_PROVIDER
99
+ : Object.prototype.hasOwnProperty.call(declared, providerId);
100
+ }
101
+ function sameInstructionsFile(leftProviderId, rightProviderId) {
102
+ if (!(0, providers_1.hasAdapter)(leftProviderId) || !(0, providers_1.hasAdapter)(rightProviderId))
103
+ return false;
104
+ return (0, providers_1.getAdapter)(leftProviderId).instructionsFilename === (0, providers_1.getAdapter)(rightProviderId).instructionsFilename;
105
+ }
106
+ /** A single managed instructions block may be shared by providers that declare
107
+ * the same project-relative instructions path. Removing or deactivating one
108
+ * provider must not remove that block while another such provider still has
109
+ * the plugin active. Codex (`AGENTS.md`) and Kimi
110
+ * (`.kimi-code/AGENTS.md`) are intentionally independent. */
111
+ function hasOtherActiveContributor(entry, providerId) {
112
+ if (!entry.providers)
113
+ return false;
114
+ return Object.entries(entry.providers).some(([candidate, state]) => candidate !== providerId &&
115
+ state.active !== false &&
116
+ sameInstructionsFile(candidate, providerId));
117
+ }
30
118
  class PluginNotFoundError extends Error {
31
119
  constructor(name) {
32
120
  super(`plugin not found in registry: ${name}`);
@@ -103,33 +191,35 @@ class PluginManager {
103
191
  });
104
192
  }
105
193
  // ─── Catalog ───────────────────────────────────────────────────────────────
106
- async listAvailable(projectPath, providerId) {
194
+ async listAvailable(projectPath, providerId, legacyProviderId) {
195
+ const provider = targetProvider(providerId);
196
+ const primaryProvider = legacyProvider(provider, legacyProviderId);
107
197
  const state = this.getProjectState(projectPath);
108
198
  const entries = [];
109
199
  // Bundled plugins, regardless of install state.
110
200
  for (const plugin of this.registry.byName.values()) {
111
201
  const m = plugin.manifest;
112
202
  const stateEntry = state.plugins[m.name];
203
+ const providerState = getProviderState(stateEntry, provider, primaryProvider);
113
204
  let status;
114
205
  // Provider applicability: a plugin is `not-applicable` when the
115
206
  // project's provider is registered, providerSupport is declared, and
116
207
  // there's no entry for this provider. Plugins that don't declare
117
208
  // providerSupport at all default to claude-compatible (preserves
118
209
  // pre-§14 behaviour for unchanged manifests).
119
- const supportsThisProvider = providerId === undefined
120
- ? true
121
- : m.providerSupport === undefined
122
- ? providerId === 'claude'
123
- : providerId in m.providerSupport;
210
+ const supportsThisProvider = supportsProvider(plugin, provider);
124
211
  if (!supportsThisProvider) {
125
212
  status = 'not-applicable';
126
213
  }
127
- else if (!stateEntry) {
214
+ else if (!providerState) {
128
215
  status = 'not-installed';
129
216
  }
130
- else if (stateEntry.health === 'degraded') {
217
+ else if (providerState.health === 'degraded') {
131
218
  status = 'degraded';
132
219
  }
220
+ else if (providerState.active === false) {
221
+ status = 'deactivated';
222
+ }
133
223
  else {
134
224
  // Plugin install lives in two files:
135
225
  // (a) state.json — the app's record that the plugin is installed
@@ -139,14 +229,14 @@ class PluginManager {
139
229
  // (b) check is skipped because the registration lives outside the
140
230
  // project filesystem (CODEX_HOME).
141
231
  let allKeysPresent = true;
142
- if (providerId === 'codex') {
232
+ if ((0, providers_1.hasAdapter)(provider) && (0, providers_1.getAdapter)(provider).mcpRegistration === 'cli-add') {
143
233
  // For codex we trust state.json — `codex mcp list` against the
144
234
  // per-project CODEX_HOME is the source of truth, but it requires a
145
235
  // subprocess which is too expensive for a catalog listing call.
146
236
  allKeysPresent = true;
147
237
  }
148
238
  else {
149
- const mcpServers = readMcpServersMap(projectPath);
239
+ const mcpServers = readMcpServersMap(projectPath, provider);
150
240
  for (const server of m.owns.mcpServers ?? []) {
151
241
  if (!(server in mcpServers)) {
152
242
  allKeysPresent = false;
@@ -160,18 +250,22 @@ class PluginManager {
160
250
  // affordance when our project-scoped install is being shadowed.
161
251
  const conflicts = [];
162
252
  const cachedDisabled = [];
163
- for (const server of m.owns.mcpServers ?? []) {
164
- for (const key of (0, claude_approval_1.findEnabledMarketplaceKeys)(server)) {
165
- if (!conflicts.includes(key))
166
- conflicts.push(key);
167
- }
168
- for (const key of (0, claude_approval_1.findInstalledButNotEnabledMarketplaceKeys)(server)) {
169
- if (!cachedDisabled.includes(key))
170
- cachedDisabled.push(key);
253
+ if (provider === 'claude') {
254
+ for (const server of m.owns.mcpServers ?? []) {
255
+ for (const key of (0, claude_approval_1.findEnabledMarketplaceKeys)(server)) {
256
+ if (!conflicts.includes(key))
257
+ conflicts.push(key);
258
+ }
259
+ for (const key of (0, claude_approval_1.findInstalledButNotEnabledMarketplaceKeys)(server)) {
260
+ if (!cachedDisabled.includes(key))
261
+ cachedDisabled.push(key);
262
+ }
171
263
  }
172
264
  }
173
265
  // Drift detection: only meaningful when actually installed.
174
- const updateAvailable = stateEntry ? (0, drift_1.detectMcpDrift)(projectPath, plugin) : false;
266
+ const updateAvailable = providerState
267
+ ? (0, drift_1.detectMcpDrift)(projectPath, plugin, provider)
268
+ : false;
175
269
  entries.push({
176
270
  name: m.name,
177
271
  version: m.version,
@@ -181,9 +275,10 @@ class PluginManager {
181
275
  requirements: m.requirements ?? [],
182
276
  owns: m.owns,
183
277
  status,
184
- installedAt: stateEntry?.installedAt,
185
- health: stateEntry?.health,
186
- healthReason: stateEntry?.healthReason,
278
+ installedAt: providerState?.installedAt,
279
+ health: providerState?.health,
280
+ healthReason: providerState?.healthReason,
281
+ providerId: provider,
187
282
  marketplaceConflicts: conflicts.length > 0 ? conflicts : undefined,
188
283
  marketplaceCachedButDisabled: cachedDisabled.length > 0 ? cachedDisabled : undefined,
189
284
  updateAvailable: updateAvailable || undefined,
@@ -193,6 +288,9 @@ class PluginManager {
193
288
  for (const [name, entry] of Object.entries(state.plugins)) {
194
289
  if (this.registry.byName.has(name))
195
290
  continue;
291
+ const providerState = getProviderState(entry, provider, primaryProvider);
292
+ if (!providerState)
293
+ continue;
196
294
  entries.push({
197
295
  name,
198
296
  version: entry.version,
@@ -201,24 +299,34 @@ class PluginManager {
201
299
  requirements: [],
202
300
  owns: {},
203
301
  status: 'orphan',
204
- installedAt: entry.installedAt,
205
- health: entry.health,
206
- healthReason: entry.healthReason,
302
+ installedAt: providerState.installedAt,
303
+ health: providerState.health,
304
+ healthReason: providerState.healthReason,
305
+ providerId: provider,
207
306
  });
208
307
  }
209
308
  return entries.sort((a, b) => a.name.localeCompare(b.name));
210
309
  }
211
310
  // ─── Preview install ───────────────────────────────────────────────────────
212
- async previewInstall(projectPath, projectId, name) {
311
+ async previewInstall(projectPath, projectId, name, providerId, slug) {
213
312
  const plugin = this.registry.byName.get(name);
214
313
  if (!plugin)
215
314
  throw new PluginNotFoundError(name);
315
+ const provider = targetProvider(providerId);
316
+ if (!supportsProvider(plugin, provider)) {
317
+ throw new PluginInstallError(`plugin '${name}' is not applicable for provider '${provider}'.`);
318
+ }
216
319
  let files;
217
320
  if (plugin.previewInstall) {
218
- files = await plugin.previewInstall({ projectPath, projectId });
321
+ files = await plugin.previewInstall({
322
+ projectPath,
323
+ projectId,
324
+ providerId: provider,
325
+ slug,
326
+ });
219
327
  }
220
328
  else {
221
- files = this._derivePreviewFiles(projectPath, plugin);
329
+ files = this._derivePreviewFiles(projectPath, plugin, provider);
222
330
  }
223
331
  const requirements = await Promise.all((plugin.manifest.requirements ?? []).map(async (req) => {
224
332
  if (this._options.checkPrerequisite) {
@@ -236,25 +344,26 @@ class PluginManager {
236
344
  platformNote,
237
345
  };
238
346
  }
239
- _derivePreviewFiles(projectPath, plugin) {
347
+ _derivePreviewFiles(projectPath, plugin, providerId) {
240
348
  const out = [];
241
349
  const m = plugin.manifest;
242
350
  // .mcp.json
243
351
  if ((m.owns.mcpServers ?? []).length > 0) {
244
- const mcpExists = fs_1.default.existsSync((0, paths_1.mcpJsonPath)(projectPath));
352
+ const mcpFile = providerMcpJsonPath(projectPath, providerId);
353
+ const mcpExists = fs_1.default.existsSync(mcpFile);
245
354
  out.push({
246
- path: '.mcp.json',
355
+ path: path_1.default.relative(projectPath, mcpFile),
247
356
  op: mcpExists ? 'modify' : 'create',
248
357
  summary: `+ mcpServers.${(m.owns.mcpServers ?? []).join(', mcpServers.')}`,
249
358
  });
250
359
  }
251
360
  // Agent fragments
252
- for (const frag of m.owns.agentFragments ?? []) {
361
+ for (const frag of providerId && providerId !== 'claude' ? [] : (m.owns.agentFragments ?? [])) {
253
362
  const exists = fs_1.default.existsSync(path_1.default.join(projectPath, frag));
254
363
  out.push({ path: frag, op: exists ? 'modify' : 'create' });
255
364
  }
256
365
  // Shared-file contributors (CLAUDE.md today, more in the future).
257
- for (const rel of (0, contributors_1.contributorPaths)(plugin)) {
366
+ for (const rel of (0, contributors_1.contributorPaths)(plugin, providerId)) {
258
367
  const exists = fs_1.default.existsSync(path_1.default.join(projectPath, rel));
259
368
  out.push({
260
369
  path: rel,
@@ -272,32 +381,34 @@ class PluginManager {
272
381
  return out;
273
382
  }
274
383
  // ─── Install ───────────────────────────────────────────────────────────────
275
- async install(projectPath, projectId, name, broadcast, providerId, slug) {
384
+ async install(projectPath, projectId, name, broadcast, providerId, slug, legacyProviderId) {
276
385
  const plugin = this.registry.byName.get(name);
277
386
  if (!plugin)
278
387
  throw new PluginNotFoundError(name);
388
+ const provider = targetProvider(providerId);
389
+ const primaryProvider = legacyProvider(provider, legacyProviderId);
279
390
  // Provider applicability gate: refuse to install a plugin that has no
280
391
  // providerSupport entry for this project's provider. Plugins that omit
281
392
  // providerSupport altogether default to claude-compatible.
282
- if (providerId !== undefined && providerId !== 'claude') {
393
+ if (!supportsProvider(plugin, provider)) {
283
394
  const declared = plugin.manifest.providerSupport;
284
- if (declared !== undefined && !(providerId in declared)) {
285
- throw new PluginInstallError(`plugin '${name}' is not applicable for provider '${providerId}'. Declared providers: ${Object.keys(declared).join(', ')}.`);
286
- }
395
+ throw new PluginInstallError(`plugin '${name}' is not applicable for provider '${provider}'. Declared providers: ${declared ? Object.keys(declared).join(', ') : LEGACY_PROVIDER}.`);
287
396
  }
288
397
  const state = this.getProjectState(projectPath);
289
- if (state.plugins[name])
398
+ const existingEntry = state.plugins[name];
399
+ if (getProviderState(existingEntry, provider, primaryProvider)) {
290
400
  throw new PluginAlreadyInstalledError(name);
401
+ }
291
402
  // Check for ownership conflicts with user-authored `.mcp.json` entries.
292
403
  // Only meaningful for `project-json` MCP registration providers (claude
293
404
  // today). Codex registers via `codex mcp add` against per-project
294
405
  // CODEX_HOME, which the plugin's install path checks via `codex mcp list`.
295
- const adapter = providerId !== undefined && (0, providers_1.hasAdapter)(providerId)
296
- ? (0, providers_1.getAdapter)(providerId)
406
+ const adapter = (0, providers_1.hasAdapter)(provider)
407
+ ? (0, providers_1.getAdapter)(provider)
297
408
  : null;
298
409
  const usesProjectJsonMcp = adapter === null || adapter.mcpRegistration === 'project-json';
299
410
  if (usesProjectJsonMcp) {
300
- const mcpFile = (0, paths_1.mcpJsonPath)(projectPath);
411
+ const mcpFile = providerMcpJsonPath(projectPath, provider);
301
412
  if (fs_1.default.existsSync(mcpFile)) {
302
413
  const raw = fs_1.default.readFileSync(mcpFile, 'utf8');
303
414
  let parsed;
@@ -307,12 +418,12 @@ class PluginManager {
307
418
  catch {
308
419
  // A hand-edited / broken `.mcp.json` should yield an actionable 409,
309
420
  // not an opaque 500 with a raw "Unexpected token" SyntaxError.
310
- throw new PluginInstallError(`cannot install '${name}': '${(0, paths_1.mcpJsonPath)(projectPath)}' is not valid JSON; fix it first.`);
421
+ throw new PluginInstallError(`cannot install '${name}': '${mcpFile}' is not valid JSON; fix it first.`);
311
422
  }
312
423
  const servers = parsed.mcpServers ?? {};
313
424
  for (const key of plugin.manifest.owns.mcpServers ?? []) {
314
425
  if (key in servers) {
315
- throw new PluginInstallError(`cannot install '${name}': '${(0, paths_1.mcpJsonPath)(projectPath)}' already has a 'mcpServers.${key}' entry. Remove it first.`);
426
+ throw new PluginInstallError(`cannot install '${name}': '${mcpFile}' already has a 'mcpServers.${key}' entry. Remove it first.`);
316
427
  }
317
428
  }
318
429
  }
@@ -320,14 +431,15 @@ class PluginManager {
320
431
  // Snapshot pre-install state of every file the plugin might touch — we
321
432
  // need exact bytes to roll back if install/verify fails.
322
433
  const targetPaths = [
323
- (0, paths_1.mcpJsonPath)(projectPath),
434
+ providerMcpJsonPath(projectPath, provider),
324
435
  (0, paths_1.stateFilePath)(projectPath),
325
- ...(plugin.manifest.owns.agentFragments ?? []).map((f) => path_1.default.join(projectPath, f)),
436
+ ...(provider !== 'claude' ? [] : (plugin.manifest.owns.agentFragments ?? []))
437
+ .map((f) => path_1.default.join(projectPath, f)),
326
438
  // Include the shared instructions file (CLAUDE.md / AGENTS.md) so a failed
327
439
  // install rolls it back too — otherwise an applyContributors write that
328
440
  // survives a later failure leaves an orphaned managed block with no state
329
441
  // entry, which uninstall can never remove (breaks byte-identical restore).
330
- ...(0, contributors_1.contributorPaths)(plugin, providerId).map((rel) => path_1.default.join(projectPath, rel)),
442
+ ...(0, contributors_1.contributorPaths)(plugin, provider).map((rel) => path_1.default.join(projectPath, rel)),
331
443
  ];
332
444
  const preState = new Map();
333
445
  for (const p of targetPaths) {
@@ -339,6 +451,7 @@ class PluginManager {
339
451
  type: 'plugin.install_progress',
340
452
  projectId,
341
453
  name,
454
+ providerId: provider,
342
455
  line,
343
456
  timestamp: new Date().toISOString(),
344
457
  };
@@ -348,7 +461,7 @@ class PluginManager {
348
461
  projectPath,
349
462
  projectId,
350
463
  slug,
351
- providerId,
464
+ providerId: provider,
352
465
  recordInstalledFile: (rel) => { installedFiles.push(rel); },
353
466
  log: onLog,
354
467
  };
@@ -356,7 +469,7 @@ class PluginManager {
356
469
  await plugin.install(ctx);
357
470
  // Verify immediately. A degraded result also triggers rollback because
358
471
  // the spec requires verify-pass before we commit state.
359
- const verify = await this._runVerify(plugin, projectPath, projectId);
472
+ const verify = await this._runVerify(plugin, projectPath, projectId, provider, slug);
360
473
  if (!verify.ok) {
361
474
  throw new PluginInstallError(`verify failed after install: ${verify.reason ?? 'unknown'}`);
362
475
  }
@@ -366,31 +479,64 @@ class PluginManager {
366
479
  // the merged `.mcp.json` entries instead of leaving them loaded forever.
367
480
  const ownedMcpServers = [...(plugin.manifest.owns.mcpServers ?? [])];
368
481
  await this.lockedUpdateState(projectPath, (s) => {
369
- const entry = {
370
- version: plugin.manifest.version,
371
- installedAt: new Date().toISOString(),
372
- installedFiles,
482
+ const installedAt = new Date().toISOString();
483
+ const providerEntry = {
484
+ installedAt,
485
+ installedFiles: [...installedFiles],
486
+ active: true,
373
487
  health: 'ok',
488
+ ownedMcpServers,
374
489
  };
375
- if (ownedMcpServers.length > 0)
376
- entry.ownedMcpServers = ownedMcpServers;
377
- s.plugins[name] = entry;
490
+ const current = s.plugins[name];
491
+ if (!current) {
492
+ const entry = {
493
+ version: plugin.manifest.version,
494
+ installedAt,
495
+ installedFiles: [...installedFiles],
496
+ health: 'ok',
497
+ };
498
+ if (ownedMcpServers.length > 0)
499
+ entry.ownedMcpServers = ownedMcpServers;
500
+ // A primary-provider-only install stays in the historical shape.
501
+ // The provider map is materialised only when it is required, keeping
502
+ // state byte-compatible for existing single-provider projects.
503
+ if (provider !== primaryProvider) {
504
+ entry.providers = { [provider]: providerEntry };
505
+ }
506
+ s.plugins[name] = entry;
507
+ return;
508
+ }
509
+ const providerStates = ensureProviderStates(current, primaryProvider);
510
+ if (providerStates[provider]) {
511
+ throw new PluginAlreadyInstalledError(name);
512
+ }
513
+ current.version = plugin.manifest.version;
514
+ providerStates[provider] = providerEntry;
515
+ syncAggregateState(current);
378
516
  });
379
517
  // No additional approval write needed: any server in `.mcp.json` loads
380
518
  // automatically when Claude opens the project. Install IS active.
381
519
  // Apply shared-file contributors (CLAUDE.md block today, more in the
382
520
  // future). Each contributor is per-plugin and idempotent.
383
- const sharedTouched = await (0, contributors_1.applyContributors)(plugin, projectPath, providerId);
521
+ const sharedTouched = await (0, contributors_1.applyContributors)(plugin, projectPath, provider);
384
522
  if (sharedTouched.length > 0) {
385
523
  for (const p of sharedTouched) {
386
524
  if (!installedFiles.includes(p))
387
525
  installedFiles.push(p);
388
526
  }
389
527
  await this.lockedUpdateState(projectPath, (s) => {
390
- if (s.plugins[name])
391
- s.plugins[name].installedFiles = installedFiles;
392
- else
528
+ const entry = s.plugins[name];
529
+ if (!entry)
393
530
  return false;
531
+ if (!entry.providers && provider === primaryProvider) {
532
+ entry.installedFiles = [...installedFiles];
533
+ return;
534
+ }
535
+ const providerState = getProviderState(entry, provider, primaryProvider);
536
+ if (!providerState)
537
+ return false;
538
+ ensureProviderStates(entry, primaryProvider)[provider].installedFiles = [...installedFiles];
539
+ syncAggregateState(entry);
394
540
  });
395
541
  }
396
542
  }
@@ -417,10 +563,10 @@ class PluginManager {
417
563
  // CODEX_HOME, NOT the snapshotted filesystem — the file rollback above
418
564
  // can't undo it. Run the plugin's uninstall so a verify-failed codex
419
565
  // install leaves no orphaned MCP registration (best-effort).
420
- if (providerId && (0, providers_1.hasAdapter)(providerId) && (0, providers_1.getAdapter)(providerId).mcpRegistration === 'cli-add') {
566
+ if ((0, providers_1.hasAdapter)(provider) && (0, providers_1.getAdapter)(provider).mcpRegistration === 'cli-add') {
421
567
  try {
422
568
  await plugin.uninstall({
423
- projectPath, projectId, slug, providerId,
569
+ projectPath, projectId, slug, providerId: provider,
424
570
  recordInstalledFile: () => { }, log: onLog,
425
571
  });
426
572
  }
@@ -433,15 +579,19 @@ class PluginManager {
433
579
  projectId,
434
580
  name,
435
581
  version: plugin.manifest.version,
582
+ providerId: provider,
436
583
  timestamp: new Date().toISOString(),
437
584
  };
438
585
  broadcast(msg);
439
586
  }
440
587
  // ─── Uninstall ─────────────────────────────────────────────────────────────
441
- async uninstall(projectPath, projectId, name, broadcast, providerId, slug) {
588
+ async uninstall(projectPath, projectId, name, broadcast, providerId, slug, legacyProviderId) {
589
+ const provider = targetProvider(providerId);
590
+ const primaryProvider = legacyProvider(provider, legacyProviderId);
442
591
  const state = this.getProjectState(projectPath);
443
592
  const entry = state.plugins[name];
444
- if (!entry)
593
+ const providerState = getProviderState(entry, provider, primaryProvider);
594
+ if (!entry || !providerState)
445
595
  throw new PluginNotInstalledError(name);
446
596
  const plugin = this.registry.byName.get(name);
447
597
  const onLog = (line) => {
@@ -449,19 +599,22 @@ class PluginManager {
449
599
  type: 'plugin.install_progress',
450
600
  projectId,
451
601
  name,
602
+ providerId: provider,
452
603
  line,
453
604
  timestamp: new Date().toISOString(),
454
605
  });
455
606
  };
456
607
  if (plugin) {
457
- // Revert shared-file contributors first so a partial uninstall doesn't
608
+ // Revert provider instruction contributors first so a partial uninstall doesn't
458
609
  // leave dangling instructions referencing missing tools.
459
- await (0, contributors_1.revertContributors)(plugin, projectPath, providerId);
610
+ if (!hasOtherActiveContributor(entry, provider)) {
611
+ await (0, contributors_1.revertContributors)(plugin, projectPath, provider);
612
+ }
460
613
  await plugin.uninstall({
461
614
  projectPath,
462
615
  projectId,
463
616
  slug,
464
- providerId,
617
+ providerId: provider,
465
618
  recordInstalledFile: () => { },
466
619
  log: onLog,
467
620
  });
@@ -472,15 +625,22 @@ class PluginManager {
472
625
  // mcpServers keys were persisted in state at install time (BUG-PLUGIN-04),
473
626
  // so even with the plugin code gone we can surgically strip the merged
474
627
  // `.mcp.json` entries instead of leaving them loaded by Claude forever.
475
- const ownedMcpServers = entry.ownedMcpServers ?? [];
628
+ const ownedMcpServers = providerState.ownedMcpServers ?? [];
476
629
  if (ownedMcpServers.length > 0) {
477
630
  try {
478
- await PluginManager.removeMcpServers(projectPath, ownedMcpServers);
631
+ await PluginManager.removeMcpServers(projectPath, ownedMcpServers, provider);
479
632
  }
480
633
  catch { /* best-effort: leave .mcp.json untouched on failure */ }
481
634
  }
482
635
  const root = path_1.default.resolve(projectPath);
483
- for (const rel of entry.installedFiles ?? []) {
636
+ const remainingFiles = new Set(Object.entries(entry.providers ?? {})
637
+ .filter(([candidate]) => candidate !== provider)
638
+ .flatMap(([, candidateState]) => candidateState.installedFiles ?? []));
639
+ for (const rel of providerState.installedFiles ?? []) {
640
+ // A managed contributor path may be referenced by more than one
641
+ // provider. Never unlink a file still owned by another provider install.
642
+ if (remainingFiles.has(rel))
643
+ continue;
484
644
  const abs = path_1.default.resolve(projectPath, rel);
485
645
  // M5: installedFiles comes from state.json, which a hostile repo can
486
646
  // ship. Without containment, `rel` of "../../../Users/victim/x" (or an
@@ -499,12 +659,29 @@ class PluginManager {
499
659
  }
500
660
  }
501
661
  await this.lockedUpdateState(projectPath, (s) => {
502
- delete s.plugins[name];
662
+ const current = s.plugins[name];
663
+ if (!current)
664
+ return false;
665
+ if (!current.providers && provider === primaryProvider) {
666
+ delete s.plugins[name];
667
+ return;
668
+ }
669
+ const providers = ensureProviderStates(current, primaryProvider);
670
+ if (!providers[provider])
671
+ return false;
672
+ delete providers[provider];
673
+ if (Object.keys(providers).length === 0) {
674
+ delete s.plugins[name];
675
+ }
676
+ else {
677
+ syncAggregateState(current);
678
+ }
503
679
  });
504
680
  broadcast({
505
681
  type: 'plugin.uninstalled',
506
682
  projectId,
507
683
  name,
684
+ providerId: provider,
508
685
  timestamp: new Date().toISOString(),
509
686
  });
510
687
  }
@@ -514,29 +691,65 @@ class PluginManager {
514
691
  * `owns.mcpServers` keys are touched; user entries are preserved. Used to
515
692
  * resolve drift surfaced by `updateAvailable`.
516
693
  */
517
- async updateMcpEntry(projectPath, projectId, name, broadcast, providerId) {
694
+ async updateMcpEntry(projectPath, projectId, name, broadcast, providerId, slug, legacyProviderId) {
518
695
  const plugin = this.registry.byName.get(name);
519
696
  if (!plugin)
520
697
  throw new PluginNotFoundError(name);
698
+ const provider = targetProvider(providerId);
699
+ const primaryProvider = legacyProvider(provider, legacyProviderId);
521
700
  const state = this.getProjectState(projectPath);
522
- if (!state.plugins[name])
701
+ const entry = state.plugins[name];
702
+ if (!getProviderState(entry, provider, primaryProvider)) {
523
703
  throw new PluginNotInstalledError(name);
704
+ }
524
705
  const expected = plugin.expectedMcpEntry?.();
525
706
  if (!expected) {
526
707
  throw new PluginInstallError(`'${name}' does not declare expectedMcpEntry; cannot update`);
527
708
  }
528
709
  const owned = plugin.manifest.owns.mcpServers ?? [];
529
- const entries = {};
530
- for (const key of owned)
531
- entries[key] = expected;
532
- await PluginManager.mergeMcpServers(projectPath, entries);
710
+ if ((0, providers_1.hasAdapter)(provider) && (0, providers_1.getAdapter)(provider).mcpRegistration === 'cli-add') {
711
+ const lifecycle = {
712
+ projectPath,
713
+ projectId,
714
+ slug,
715
+ providerId: provider,
716
+ recordInstalledFile: () => { },
717
+ log: () => { },
718
+ };
719
+ await plugin.uninstall(lifecycle);
720
+ await plugin.install(lifecycle);
721
+ }
722
+ else {
723
+ const entries = {};
724
+ for (const key of owned)
725
+ entries[key] = expected;
726
+ await PluginManager.mergeMcpServers(projectPath, entries, provider);
727
+ }
533
728
  // Refresh shared-file contributions too: a drift may exist in CLAUDE.md
534
729
  // even when the .mcp.json entry matches.
535
- await (0, contributors_1.applyContributors)(plugin, projectPath, providerId);
730
+ await (0, contributors_1.applyContributors)(plugin, projectPath, provider);
731
+ await this.lockedUpdateState(projectPath, (s) => {
732
+ const current = s.plugins[name];
733
+ if (!current)
734
+ return false;
735
+ if (!current.providers && provider === primaryProvider) {
736
+ current.health = 'unknown';
737
+ delete current.healthReason;
738
+ return;
739
+ }
740
+ const providerState = getProviderState(current, provider, primaryProvider);
741
+ if (!providerState)
742
+ return false;
743
+ const mutable = ensureProviderStates(current, primaryProvider)[provider];
744
+ mutable.health = 'unknown';
745
+ delete mutable.healthReason;
746
+ syncAggregateState(current);
747
+ });
536
748
  broadcast({
537
749
  type: 'plugin.health_changed',
538
750
  projectId,
539
751
  name,
752
+ providerId: provider,
540
753
  status: 'unknown',
541
754
  reason: 'updated',
542
755
  timestamp: new Date().toISOString(),
@@ -556,13 +769,17 @@ class PluginManager {
556
769
  * Plugin install state survives across toggles. Uninstall is the only
557
770
  * action that clears state.json + custom-*.md fragments.
558
771
  */
559
- async setActive(projectPath, projectId, name, active, broadcast, providerId) {
772
+ async setActive(projectPath, projectId, name, active, broadcast, providerId, slug, legacyProviderId) {
560
773
  const plugin = this.registry.byName.get(name);
561
774
  if (!plugin)
562
775
  throw new PluginNotFoundError(name);
776
+ const provider = targetProvider(providerId);
777
+ const primaryProvider = legacyProvider(provider, legacyProviderId);
563
778
  const state = this.getProjectState(projectPath);
564
- if (!state.plugins[name])
779
+ const entry = state.plugins[name];
780
+ if (!entry || !getProviderState(entry, provider, primaryProvider)) {
565
781
  throw new PluginNotInstalledError(name);
782
+ }
566
783
  const owned = plugin.manifest.owns.mcpServers ?? [];
567
784
  if (owned.length === 0) {
568
785
  throw new PluginInstallError(`'${name}' owns no mcpServers; cannot toggle activation`);
@@ -572,47 +789,100 @@ class PluginManager {
572
789
  if (!expected) {
573
790
  throw new PluginInstallError(`'${name}' does not declare expectedMcpEntry; cannot activate`);
574
791
  }
575
- const entries = {};
576
- for (const k of owned)
577
- entries[k] = expected;
578
- await PluginManager.mergeMcpServers(projectPath, entries);
579
- await (0, contributors_1.applyContributors)(plugin, projectPath, providerId);
792
+ if ((0, providers_1.hasAdapter)(provider) && (0, providers_1.getAdapter)(provider).mcpRegistration === 'cli-add') {
793
+ await plugin.install({
794
+ projectPath,
795
+ projectId,
796
+ slug,
797
+ providerId: provider,
798
+ recordInstalledFile: () => { },
799
+ log: () => { },
800
+ });
801
+ }
802
+ else {
803
+ const entries = {};
804
+ for (const k of owned)
805
+ entries[k] = expected;
806
+ await PluginManager.mergeMcpServers(projectPath, entries, provider);
807
+ }
808
+ await (0, contributors_1.applyContributors)(plugin, projectPath, provider);
580
809
  }
581
810
  else {
582
- await PluginManager.removeMcpServers(projectPath, owned);
583
- await (0, contributors_1.revertContributors)(plugin, projectPath, providerId);
811
+ if ((0, providers_1.hasAdapter)(provider) && (0, providers_1.getAdapter)(provider).mcpRegistration === 'cli-add') {
812
+ await plugin.uninstall({
813
+ projectPath,
814
+ projectId,
815
+ slug,
816
+ providerId: provider,
817
+ recordInstalledFile: () => { },
818
+ log: () => { },
819
+ });
820
+ }
821
+ else {
822
+ await PluginManager.removeMcpServers(projectPath, owned, provider);
823
+ }
824
+ if (!hasOtherActiveContributor(entry, provider)) {
825
+ await (0, contributors_1.revertContributors)(plugin, projectPath, provider);
826
+ }
584
827
  }
828
+ await this.lockedUpdateState(projectPath, (s) => {
829
+ const current = s.plugins[name];
830
+ if (!current)
831
+ return false;
832
+ if (!current.providers && provider === primaryProvider) {
833
+ // Legacy state has no explicit active field. Materialise only when a
834
+ // value is needed, preserving legacy reads while making the toggle
835
+ // durable.
836
+ const providerState = ensureProviderStates(current, primaryProvider)[provider];
837
+ providerState.active = active;
838
+ providerState.health = active ? 'ok' : 'unknown';
839
+ delete providerState.healthReason;
840
+ syncAggregateState(current);
841
+ return;
842
+ }
843
+ const providerState = getProviderState(current, provider, primaryProvider);
844
+ if (!providerState)
845
+ return false;
846
+ const mutable = ensureProviderStates(current, primaryProvider)[provider];
847
+ mutable.active = active;
848
+ mutable.health = active ? 'ok' : 'unknown';
849
+ delete mutable.healthReason;
850
+ syncAggregateState(current);
851
+ });
585
852
  broadcast({
586
853
  type: 'plugin.health_changed',
587
854
  projectId,
588
855
  name,
856
+ providerId: provider,
589
857
  status: active ? 'ok' : 'unknown',
590
858
  reason: active ? 'activated' : 'deactivated',
591
859
  timestamp: new Date().toISOString(),
592
860
  });
593
861
  }
594
862
  /** Drop a state.json entry for a plugin no longer in the registry. */
595
- async removeOrphan(projectPath, projectId, name, broadcast) {
863
+ async removeOrphan(projectPath, projectId, name, broadcast, providerId, slug, legacyProviderId) {
596
864
  if (this.registry.byName.has(name)) {
597
865
  throw new PluginInstallError(`'${name}' is not orphan; it is still bundled. Use uninstall instead.`);
598
866
  }
599
- return this.uninstall(projectPath, projectId, name, broadcast);
867
+ return this.uninstall(projectPath, projectId, name, broadcast, providerId, slug, legacyProviderId);
600
868
  }
601
869
  // ─── Verify ────────────────────────────────────────────────────────────────
602
- async verify(projectPath, projectId, name, broadcast) {
870
+ async verify(projectPath, projectId, name, broadcast, providerId, legacyProviderId, slug) {
603
871
  const plugin = this.registry.byName.get(name);
604
872
  if (!plugin)
605
873
  throw new PluginNotFoundError(name);
606
- const result = await this._runVerify(plugin, projectPath, projectId);
607
- await this._cacheHealth(projectPath, projectId, name, result, broadcast);
874
+ const provider = targetProvider(providerId);
875
+ const primaryProvider = legacyProvider(provider, legacyProviderId);
876
+ const result = await this._runVerify(plugin, projectPath, projectId, provider, slug);
877
+ await this._cacheHealth(projectPath, projectId, name, result, broadcast, provider, primaryProvider);
608
878
  return result;
609
879
  }
610
- async _runVerify(plugin, projectPath, projectId) {
880
+ async _runVerify(plugin, projectPath, projectId, providerId, slug) {
611
881
  const timeout = plugin.manifest.verifyTimeoutMs ?? this._options.defaultVerifyTimeoutMs;
612
882
  const checkedAt = new Date().toISOString();
613
883
  try {
614
884
  const result = await Promise.race([
615
- plugin.verify({ projectPath, projectId }),
885
+ plugin.verify({ projectPath, projectId, providerId, slug }),
616
886
  new Promise((resolve) => setTimeout(() => resolve({ __timeout: true }), timeout).unref?.()),
617
887
  ]);
618
888
  if ('__timeout' in result) {
@@ -624,7 +894,9 @@ class PluginManager {
624
894
  return { ok: false, reason: `verify-exception: ${err?.message ?? String(err)}`, checkedAt };
625
895
  }
626
896
  }
627
- async _cacheHealth(projectPath, projectId, name, result, broadcast) {
897
+ async _cacheHealth(projectPath, projectId, name, result, broadcast, providerId, legacyProviderId) {
898
+ const provider = targetProvider(providerId);
899
+ const primaryProvider = legacyProvider(provider, legacyProviderId);
628
900
  const newHealth = result.ok ? 'ok' : 'degraded';
629
901
  // Read → compare → write under ONE lock so concurrent verifies on the same
630
902
  // project (resolvePluginsForSpawn runs every installed plugin's verify in
@@ -636,11 +908,22 @@ class PluginManager {
636
908
  const entry = state.plugins[name];
637
909
  if (!entry)
638
910
  return false;
639
- const changed = entry.health !== newHealth || entry.healthReason !== result.reason;
911
+ const providerState = getProviderState(entry, provider, primaryProvider);
912
+ if (!providerState)
913
+ return false;
914
+ const changed = providerState.health !== newHealth || providerState.healthReason !== result.reason;
640
915
  if (!changed)
641
916
  return false; // nothing to persist — avoids per-spawn write churn (verify runs on every rail spawn)
642
- entry.health = newHealth;
643
- entry.healthReason = result.reason;
917
+ if (!entry.providers && provider === primaryProvider) {
918
+ entry.health = newHealth;
919
+ entry.healthReason = result.reason;
920
+ }
921
+ else {
922
+ const mutable = ensureProviderStates(entry, primaryProvider)[provider];
923
+ mutable.health = newHealth;
924
+ mutable.healthReason = result.reason;
925
+ syncAggregateState(entry);
926
+ }
644
927
  didChange = true;
645
928
  });
646
929
  if (!didChange)
@@ -650,6 +933,7 @@ class PluginManager {
650
933
  type: 'plugin.health_changed',
651
934
  projectId,
652
935
  name,
936
+ providerId: provider,
653
937
  status: newHealth,
654
938
  reason: result.reason,
655
939
  timestamp: new Date().toISOString(),
@@ -659,8 +943,9 @@ class PluginManager {
659
943
  }
660
944
  // ─── Surgical helpers exposed to plugins ───────────────────────────────────
661
945
  /** Helper: surgically merge `mcpServers.<key>` entries into .mcp.json. */
662
- static async mergeMcpServers(projectPath, entries) {
663
- await (0, json_mutation_1.surgicalMergeJson)((0, paths_1.mcpJsonPath)(projectPath), (current) => {
946
+ static async mergeMcpServers(projectPath, entries, providerId) {
947
+ const mcpFile = providerMcpJsonPath(projectPath, providerId);
948
+ await (0, json_mutation_1.surgicalMergeJson)(mcpFile, (current) => {
664
949
  const next = (current ?? {});
665
950
  // BUG-PLUGIN-05: `mcpServers` MUST be a plain object. The old `?? {}`
666
951
  // fallback only covered null/undefined — if it was a JSON array (or any
@@ -673,7 +958,7 @@ class PluginManager {
673
958
  raw === null ||
674
959
  (typeof raw === 'object' && !Array.isArray(raw));
675
960
  if (!isPlainObject) {
676
- throw new PluginInstallError(`cannot merge mcpServers into '${(0, paths_1.mcpJsonPath)(projectPath)}': ` +
961
+ throw new PluginInstallError(`cannot merge mcpServers into '${mcpFile}': ` +
677
962
  `'mcpServers' must be a JSON object but is ${Array.isArray(raw) ? 'an array' : typeof raw}; fix it first.`);
678
963
  }
679
964
  const servers = (raw ?? {});
@@ -684,10 +969,10 @@ class PluginManager {
684
969
  });
685
970
  }
686
971
  /** Helper: remove specific `mcpServers.<key>` entries from .mcp.json. */
687
- static async removeMcpServers(projectPath, keys) {
972
+ static async removeMcpServers(projectPath, keys, providerId) {
688
973
  if (keys.length === 0)
689
974
  return;
690
- await (0, json_mutation_1.surgicalRemoveKeys)((0, paths_1.mcpJsonPath)(projectPath), keys.map((k) => `mcpServers.${k}`));
975
+ await (0, json_mutation_1.surgicalRemoveKeys)(providerMcpJsonPath(projectPath, providerId), keys.map((k) => `mcpServers.${k}`));
691
976
  }
692
977
  }
693
978
  exports.PluginManager = PluginManager;