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
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.providerSupportsAgentStudioAutomation = providerSupportsAgentStudioAutomation;
6
7
  exports.createProfilesRouter = createProfilesRouter;
7
8
  const fs_1 = __importDefault(require("fs"));
8
9
  const path_1 = __importDefault(require("path"));
@@ -11,27 +12,85 @@ const agent_generator_1 = require("./agent-generator");
11
12
  const agent_refine_db_1 = require("./agent-refine-db");
12
13
  const agent_refine_manager_1 = require("./agent-refine-manager");
13
14
  const providers_1 = require("./providers");
15
+ const kimi_skill_prompt_1 = require("./providers/kimi-skill-prompt");
14
16
  const profile_manager_1 = require("./profile-manager");
15
17
  const workspace_resolution_1 = require("./workspace-resolution");
18
+ const project_router_helpers_1 = require("./project-router-helpers");
16
19
  /**
17
20
  * Relocate-artifacts gate: the dir whose `.specrails/{profiles,specrails-version}`
18
- * AND whose `.claude/agents/**` catalog the profiles surface reads/writes.
19
- * Relocated ⇒ the workspace dir (where core assembled the agents catalog +
21
+ * and provider-native roles catalog the profiles surface reads/writes.
22
+ * Relocated ⇒ the workspace dir (where Core assembled the roles catalog +
20
23
  * `.specrails/profiles`); legacy ⇒ project.path (byte-identical to today).
21
24
  *
22
- * The `.claude/agents/**` catalog MUST follow this same root: when relocated,
23
- * core materializes the agents into `<workspace>/.claude/agents`, so a profiles
24
- * catalog read/write rooted at `project.path` would (a) read a stale/absent repo
25
- * copy and (b) write custom-* agents into the repo — violating repo-immutability
26
- * and never reaching the workspace the rails actually load from.
25
+ * The roles catalog MUST follow this same root. Depending on the selected
26
+ * adapter, Core materializes file-based roles such as
27
+ * `<workspace>/.claude/agents/<id>.md`, or skill-based roles such as
28
+ * `<workspace>/.kimi-code/skills/<id>/SKILL.md`. Reading/writing beneath
29
+ * `project.path` would miss the execution catalog and violate repo immutability.
27
30
  */
28
31
  function specRoot(project) {
29
32
  const exec = (0, workspace_resolution_1.resolveProjectExecution)({ slug: project.slug, path: project.path });
30
33
  return exec.relocated && exec.workspaceDir ? exec.workspaceDir : project.path;
31
34
  }
32
- /** The agents-catalog directory for the relocate-aware root (`<root>/.claude/agents`). */
33
- function agentsCatalogDir(project) {
34
- return path_1.default.join(specRoot(project), '.claude', 'agents');
35
+ function installedProviders(project) {
36
+ const primary = project.provider ?? 'claude';
37
+ const configured = project.providers?.filter((provider) => typeof provider === 'string');
38
+ return configured?.length ? [...new Set([primary, ...configured])] : [primary];
39
+ }
40
+ function requestedProvider(req, project, bodyProvider) {
41
+ const queryProvider = typeof req.query.provider === 'string' ? req.query.provider : undefined;
42
+ const explicitBodyProvider = typeof bodyProvider === 'string' && bodyProvider.length > 0
43
+ ? bodyProvider
44
+ : undefined;
45
+ if (queryProvider && explicitBodyProvider && queryProvider !== explicitBodyProvider) {
46
+ throw new profile_manager_1.ProfileValidationError([
47
+ `body provider '${explicitBodyProvider}' does not match requested provider '${queryProvider}'`,
48
+ ]);
49
+ }
50
+ const provider = queryProvider ?? explicitBodyProvider ?? project.provider ?? 'claude';
51
+ if (!(0, providers_1.hasAdapter)(provider) || !installedProviders(project).includes(provider)) {
52
+ throw new profile_manager_1.ProfileValidationError([
53
+ `provider '${provider}' is not installed for this project`,
54
+ ]);
55
+ }
56
+ return provider;
57
+ }
58
+ function projectAdapter(project, provider) {
59
+ return (0, providers_1.getAdapter)(provider ?? project.provider ?? 'claude');
60
+ }
61
+ function agentFile(project, agentId, provider) {
62
+ const root = specRoot(project);
63
+ const adapter = projectAdapter(project, provider);
64
+ return adapter.customRolePath?.(root, agentId)
65
+ ?? path_1.default.join(root, adapter.projectDirName, 'agents', `${agentId}.md`);
66
+ }
67
+ /** The provider-native roles catalog directory. */
68
+ function agentsCatalogDir(project, provider) {
69
+ const probe = agentFile(project, '__catalog_probe__', provider);
70
+ // File-based roles use `<catalog>/<id>.md`; skill-based roles (Kimi) use
71
+ // `<catalog>/<id>/SKILL.md`.
72
+ return path_1.default.basename(probe) === 'SKILL.md'
73
+ ? path_1.default.dirname(path_1.default.dirname(probe))
74
+ : path_1.default.dirname(probe);
75
+ }
76
+ function listAgentFiles(project, provider) {
77
+ const dir = agentsCatalogDir(project, provider);
78
+ if (!fs_1.default.existsSync(dir))
79
+ return [];
80
+ const out = [];
81
+ for (const entry of fs_1.default.readdirSync(dir, { withFileTypes: true })) {
82
+ const id = entry.isDirectory()
83
+ ? entry.name
84
+ : entry.isFile() && entry.name.endsWith('.md')
85
+ ? entry.name.slice(0, -3)
86
+ : null;
87
+ if (!id)
88
+ continue;
89
+ const file = agentFile(project, id, provider);
90
+ if (fs_1.default.existsSync(file))
91
+ out.push({ id, file });
92
+ }
93
+ return out;
35
94
  }
36
95
  const AGENTS_SECTION_ENABLED = process.env.SPECRAILS_AGENTS_SECTION !== 'false';
37
96
  function handleError(res, err) {
@@ -50,6 +109,35 @@ function handleError(res, err) {
50
109
  const message = err instanceof Error ? err.message : 'unknown error';
51
110
  res.status(500).json({ error: message });
52
111
  }
112
+ /** Studio automation is safe when the CLI can enforce either a no-tools or a
113
+ * read-only boundary. Codex/Gemini use their verified native read-only modes;
114
+ * Kimi prompt mode exposes neither and therefore fails closed. */
115
+ function providerSupportsAgentStudioAutomation(provider) {
116
+ const adapter = (0, providers_1.getAdapter)(provider);
117
+ const policies = adapter.capabilities.toolPolicies ?? [];
118
+ return policies.includes('none') || policies.includes('read-only');
119
+ }
120
+ function requireSafeStudioPolicy(res, provider) {
121
+ if (providerSupportsAgentStudioAutomation(provider))
122
+ return true;
123
+ res.status(409).json({
124
+ error: 'provider_tool_policy_unsupported',
125
+ provider,
126
+ requiredPolicies: ['none', 'read-only'],
127
+ });
128
+ return false;
129
+ }
130
+ /** Kimi custom roles are provider-native Skills, so a non-empty markdown file
131
+ * is not enough: discovery requires valid identifying frontmatter. Reuse the
132
+ * execution parser directly so Profiles can never persist regex-accepted YAML
133
+ * that the Kimi headless path rejects later. The parser module is dependency
134
+ * leaf (fs/path/js-yaml only), so this direct import does not create a router /
135
+ * adapter cycle. */
136
+ function validateCustomRoleBody(provider, agentId, body) {
137
+ if (provider !== 'kimi')
138
+ return [];
139
+ return (0, kimi_skill_prompt_1.validateKimiRoleDocument)(body, agentId, `${agentId}/SKILL.md`);
140
+ }
53
141
  function createProfilesRouter() {
54
142
  const router = (0, express_1.Router)({ mergeParams: true });
55
143
  function ctx(req) {
@@ -63,41 +151,75 @@ function createProfilesRouter() {
63
151
  }
64
152
  next();
65
153
  });
154
+ // GET /api/projects/:projectId/profiles/context
155
+ // Provider-aware catalogs drive both Profiles and Agent Studio without
156
+ // hard-coding a Claude fallback into either client.
157
+ router.get('/context', (req, res) => {
158
+ try {
159
+ const { project } = ctx(req);
160
+ const providers = installedProviders(project);
161
+ const catalogs = Object.fromEntries(providers.map((provider) => {
162
+ const adapter = (0, providers_1.getAdapter)(provider);
163
+ return [provider, {
164
+ models: adapter.modelCatalog().map(({ value, label }) => ({ value, label })),
165
+ defaultModel: adapter.defaultModel(),
166
+ baselineAgents: [...adapter.baselineAgents()],
167
+ customModelAliases: adapter.capabilities.customModelAliases === true,
168
+ }];
169
+ }));
170
+ res.json({
171
+ primaryProvider: project.provider ?? providers[0] ?? 'claude',
172
+ providers,
173
+ catalogs,
174
+ });
175
+ }
176
+ catch (err) {
177
+ handleError(res, err);
178
+ }
179
+ });
66
180
  // POST /api/projects/:projectId/profiles/migrate-from-settings
67
181
  // Seed a `default` profile from the agent frontmatter + legacy routing.
68
182
  // Intended for first-time onboarding of existing projects.
69
183
  router.post('/migrate-from-settings', (req, res) => {
70
184
  try {
71
185
  const { project, broadcast } = ctx(req);
72
- const agentsDir = agentsCatalogDir(project);
186
+ const provider = requestedProvider(req, project);
187
+ const adapter = projectAdapter(project, provider);
188
+ const configuredKimiModels = provider === 'kimi'
189
+ ? (0, project_router_helpers_1.readAgentModelSelection)({ ...project, provider })
190
+ : null;
191
+ const projectedKimiModels = configuredKimiModels
192
+ ? new Map((0, project_router_helpers_1.readAgentModels)({ ...project, provider })
193
+ .map((entry) => [entry.name, entry.model]))
194
+ : null;
195
+ const agentsDir = agentsCatalogDir(project, provider);
73
196
  if (!fs_1.default.existsSync(agentsDir)) {
74
- res.status(400).json({ error: 'no .claude/agents/ directory found' });
197
+ res.status(400).json({ error: `no ${adapter.projectDirName} roles catalog found` });
75
198
  return;
76
199
  }
77
- // Gather installed sr-*.md with their declared models.
78
200
  const agents = [];
79
- for (const entry of fs_1.default.readdirSync(agentsDir)) {
80
- if (!entry.endsWith('.md'))
201
+ for (const entry of listAgentFiles(project, provider)) {
202
+ if (!entry.id.startsWith('sr-'))
81
203
  continue;
82
- if (!entry.startsWith('sr-'))
83
- continue;
84
- const id = entry.slice(0, -'.md'.length);
85
- let model = 'sonnet';
86
- try {
87
- const content = fs_1.default.readFileSync(path_1.default.join(agentsDir, entry), 'utf8');
88
- const fm = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
89
- if (fm) {
90
- const m = fm[1].match(/^model:\s*(sonnet|opus|haiku)/m);
91
- if (m)
92
- model = m[1];
204
+ const id = entry.id;
205
+ let model = projectedKimiModels?.get(id) ?? adapter.defaultModel();
206
+ if (!projectedKimiModels) {
207
+ try {
208
+ const content = fs_1.default.readFileSync(entry.file, 'utf8');
209
+ const fm = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
210
+ if (fm) {
211
+ const m = fm[1].match(/^model:\s*(\S+)/m);
212
+ if (m && (0, providers_1.isModelAvailableForAdapter)(adapter, m[1]))
213
+ model = m[1];
214
+ }
215
+ }
216
+ catch {
217
+ // skip unreadable files
93
218
  }
94
- }
95
- catch {
96
- // skip unreadable files
97
219
  }
98
220
  agents.push({ id, model });
99
221
  }
100
- const baseline = ['sr-architect', 'sr-developer', 'sr-reviewer'];
222
+ const baseline = [...adapter.baselineAgents()];
101
223
  const missing = baseline.filter((id) => !agents.some((a) => a.id === id));
102
224
  if (missing.length > 0) {
103
225
  res.status(400).json({
@@ -120,23 +242,23 @@ function createProfilesRouter() {
120
242
  .sort((a, b) => a.id.localeCompare(b.id)),
121
243
  ...agents.filter((a) => pinnedLast.has(a.id)),
122
244
  ];
123
- // Build the default profile mirroring legacy routing. The frontmatter
124
- // model aliases parsed above are claude-specific; for any other provider
125
- // they are not in the adapter's catalog, so fall back to that provider's
126
- // default model and stamp the provider so the persisted profile validates
127
- // against the right catalog on every future read.
128
- const provider = project.provider ?? 'claude';
245
+ // Build the default profile mirroring legacy routing. Claude keeps its
246
+ // role-frontmatter projection. Kimi has no compatible `model:` field in
247
+ // SKILL.md, so its exact validated default/overrides come from the
248
+ // provider install config projected above.
129
249
  const isClaude = provider === 'claude';
130
- const fallbackModel = isClaude ? 'sonnet' : (0, providers_1.getAdapter)(provider).defaultModel();
250
+ const fallbackModel = adapter.defaultModel();
131
251
  const profile = {
132
252
  schemaVersion: 1,
133
253
  name: 'default',
134
254
  description: 'Baseline profile migrated from your current agent frontmatters.',
135
- ...(isClaude ? {} : { provider }),
136
- orchestrator: { model: isClaude ? 'sonnet' : fallbackModel },
255
+ ...(!isClaude || installedProviders(project).length > 1 ? { provider } : {}),
256
+ orchestrator: {
257
+ model: configuredKimiModels?.defaultModel ?? fallbackModel,
258
+ },
137
259
  agents: orderedAgents.map((a) => ({
138
260
  id: a.id,
139
- model: isClaude ? a.model : fallbackModel,
261
+ model: a.model,
140
262
  required: baseline.includes(a.id),
141
263
  })),
142
264
  routing: [
@@ -170,7 +292,8 @@ function createProfilesRouter() {
170
292
  // Per-profile aggregated metrics over the requested time window.
171
293
  router.get('/analytics', (req, res) => {
172
294
  try {
173
- const { db } = ctx(req);
295
+ const { db, project } = ctx(req);
296
+ const provider = requestedProvider(req, project);
174
297
  const windowDays = Math.max(1, Math.min(365, parseInt((req.query.windowDays ?? '30'), 10) || 30));
175
298
  const since = Date.now() - windowDays * 24 * 60 * 60 * 1000;
176
299
  const rows = db
@@ -179,16 +302,30 @@ function createProfilesRouter() {
179
302
  COUNT(*) AS jobs,
180
303
  SUM(CASE WHEN j.status = 'completed' THEN 1 ELSE 0 END) AS succeeded,
181
304
  AVG(j.duration_ms) AS avgDurationMs,
182
- AVG(COALESCE(j.tokens_in, 0) + COALESCE(j.tokens_out, 0)
183
- + COALESCE(j.tokens_cache_read, 0) + COALESCE(j.tokens_cache_create, 0)) AS avgTokens,
184
- AVG(j.total_cost_usd) AS avgCostUsd
305
+ AVG(CASE
306
+ WHEN j.tokens_in IS NOT NULL OR j.tokens_out IS NOT NULL
307
+ OR j.tokens_cache_read IS NOT NULL OR j.tokens_cache_create IS NOT NULL
308
+ THEN COALESCE(j.tokens_in, 0) + COALESCE(j.tokens_out, 0)
309
+ + COALESCE(j.tokens_cache_read, 0) + COALESCE(j.tokens_cache_create, 0)
310
+ END) AS avgTokens,
311
+ AVG(j.total_cost_usd) AS avgCostUsd,
312
+ SUM(CASE WHEN j.tokens_in IS NOT NULL OR j.tokens_out IS NOT NULL
313
+ OR j.tokens_cache_read IS NOT NULL OR j.tokens_cache_create IS NOT NULL
314
+ THEN 1 ELSE 0 END) AS usageReportedJobs,
315
+ SUM(CASE WHEN j.tokens_in IS NULL AND j.tokens_out IS NULL
316
+ AND j.tokens_cache_read IS NULL AND j.tokens_cache_create IS NULL
317
+ THEN 1 ELSE 0 END) AS usageUnavailableJobs,
318
+ COUNT(j.total_cost_usd) AS pricedJobs,
319
+ SUM(CASE WHEN j.total_cost_usd IS NULL THEN 1 ELSE 0 END) AS unpricedJobs
185
320
  FROM job_profiles jp
186
321
  JOIN jobs j ON j.id = jp.job_id
187
322
  WHERE jp.created_at >= ?
323
+ AND COALESCE(j.provider, 'claude') = ?
188
324
  GROUP BY jp.profile_name
189
325
  ORDER BY jobs DESC`)
190
- .all(since);
326
+ .all(since, provider);
191
327
  res.json({
328
+ provider,
192
329
  windowDays,
193
330
  rows: rows.map((r) => ({
194
331
  profileName: r.profileName,
@@ -198,6 +335,10 @@ function createProfilesRouter() {
198
335
  avgDurationMs: r.avgDurationMs,
199
336
  avgTokens: r.avgTokens,
200
337
  avgCostUsd: r.avgCostUsd,
338
+ usageReportedJobs: r.usageReportedJobs,
339
+ usageUnavailableJobs: r.usageUnavailableJobs,
340
+ pricedJobs: r.pricedJobs,
341
+ unpricedJobs: r.unpricedJobs,
201
342
  })),
202
343
  });
203
344
  }
@@ -248,20 +389,19 @@ function createProfilesRouter() {
248
389
  }
249
390
  });
250
391
  // GET /api/projects/:projectId/profiles/catalog
251
- // List all agents available in .claude/agents/ (upstream sr-* and custom custom-*)
392
+ // List all roles in the selected provider-native catalog (sr-* + custom-*).
252
393
  router.get('/catalog', (req, res) => {
253
394
  try {
254
395
  const { project } = ctx(req);
255
- const dir = agentsCatalogDir(project);
256
- if (!fs_1.default.existsSync(dir)) {
396
+ const provider = requestedProvider(req, project);
397
+ const files = listAgentFiles(project, provider);
398
+ if (files.length === 0) {
257
399
  res.json({ agents: [] });
258
400
  return;
259
401
  }
260
402
  const agents = [];
261
- for (const file of fs_1.default.readdirSync(dir)) {
262
- if (!file.endsWith('.md'))
263
- continue;
264
- const id = file.slice(0, -'.md'.length);
403
+ for (const entry of files) {
404
+ const id = entry.id;
265
405
  const kind = id.startsWith('sr-')
266
406
  ? 'upstream'
267
407
  : id.startsWith('custom-')
@@ -272,9 +412,16 @@ function createProfilesRouter() {
272
412
  let description;
273
413
  let model;
274
414
  try {
275
- const body = fs_1.default.readFileSync(path_1.default.join(dir, file), 'utf8');
276
- const fm = body.match(/^---\r?\n([\s\S]*?)\r?\n---/);
277
- if (fm) {
415
+ const body = fs_1.default.readFileSync(entry.file, 'utf8');
416
+ if (provider === 'kimi') {
417
+ // Use the same js-yaml metadata parser as validation/execution so
418
+ // folded/literal descriptions and quoted scalars render correctly.
419
+ description = (0, kimi_skill_prompt_1.parseKimiSkillDocument)(body, entry.file).description;
420
+ }
421
+ else {
422
+ const fm = body.match(/^---\r?\n([\s\S]*?)\r?\n---/);
423
+ if (!fm)
424
+ throw new Error(`Missing frontmatter in ${entry.file}`);
278
425
  // description can be a long JSON-escaped string spanning multiple lines.
279
426
  // Match from `description:` up to the next top-level YAML key or the end
280
427
  // of the frontmatter block. Then unescape \n, \t, \" and strip surrounding
@@ -304,6 +451,9 @@ function createProfilesRouter() {
304
451
  if (modelMatch)
305
452
  model = modelMatch[1];
306
453
  }
454
+ if (description && description.length > 280) {
455
+ description = description.slice(0, 277) + '…';
456
+ }
307
457
  }
308
458
  catch {
309
459
  // ignore unreadable files
@@ -322,12 +472,13 @@ function createProfilesRouter() {
322
472
  router.get('/catalog/:agentId', (req, res) => {
323
473
  try {
324
474
  const { project } = ctx(req);
475
+ const provider = requestedProvider(req, project);
325
476
  const agentId = req.params.agentId;
326
477
  if (!/^(sr|custom)-[a-z0-9][a-z0-9-]*$/.test(agentId)) {
327
478
  res.status(400).json({ error: 'invalid agent id' });
328
479
  return;
329
480
  }
330
- const file = path_1.default.join(agentsCatalogDir(project), `${agentId}.md`);
481
+ const file = agentFile(project, agentId, provider);
331
482
  if (!fs_1.default.existsSync(file)) {
332
483
  res.status(404).json({ error: 'agent not found' });
333
484
  return;
@@ -345,6 +496,7 @@ function createProfilesRouter() {
345
496
  router.post('/catalog', (req, res) => {
346
497
  try {
347
498
  const { project, db, broadcast } = ctx(req);
499
+ const provider = requestedProvider(req, project, req.body?.provider);
348
500
  const id = (req.body?.id ?? '').toString().trim();
349
501
  const body = (req.body?.body ?? '').toString();
350
502
  if (!/^custom-[a-z0-9][a-z0-9-]*$/.test(id)) {
@@ -355,9 +507,13 @@ function createProfilesRouter() {
355
507
  res.status(400).json({ error: 'body is required' });
356
508
  return;
357
509
  }
358
- const agentsDir = agentsCatalogDir(project);
359
- fs_1.default.mkdirSync(agentsDir, { recursive: true });
360
- const file = path_1.default.join(agentsDir, `${id}.md`);
510
+ const roleErrors = validateCustomRoleBody(provider, id, body);
511
+ if (roleErrors.length > 0) {
512
+ res.status(400).json({ error: 'invalid_kimi_skill', details: roleErrors });
513
+ return;
514
+ }
515
+ const file = agentFile(project, id, provider);
516
+ fs_1.default.mkdirSync(path_1.default.dirname(file), { recursive: true });
361
517
  if (fs_1.default.existsSync(file)) {
362
518
  res.status(409).json({ error: `agent '${id}' already exists` });
363
519
  return;
@@ -365,7 +521,8 @@ function createProfilesRouter() {
365
521
  fs_1.default.writeFileSync(file, body, 'utf8');
366
522
  // Record initial version
367
523
  const nextVersion = 1;
368
- db.prepare(`INSERT INTO agent_versions (agent_name, version, body, created_at) VALUES (?, ?, ?, ?)`).run(id, nextVersion, body, Date.now());
524
+ db.prepare(`INSERT INTO agent_versions (provider, agent_name, version, body, created_at)
525
+ VALUES (?, ?, ?, ?, ?)`).run(provider, id, nextVersion, body, Date.now());
369
526
  broadcast({ type: 'agent.changed', projectId: project.id, id });
370
527
  res.status(201).json({ id, body, version: nextVersion });
371
528
  }
@@ -378,6 +535,7 @@ function createProfilesRouter() {
378
535
  router.patch('/catalog/:agentId', (req, res) => {
379
536
  try {
380
537
  const { project, db, broadcast } = ctx(req);
538
+ const provider = requestedProvider(req, project, req.body?.provider);
381
539
  const agentId = req.params.agentId;
382
540
  if (!/^custom-[a-z0-9][a-z0-9-]*$/.test(agentId)) {
383
541
  res.status(403).json({ error: 'only custom-* agents can be edited from the app' });
@@ -388,17 +546,24 @@ function createProfilesRouter() {
388
546
  res.status(400).json({ error: 'body is required' });
389
547
  return;
390
548
  }
391
- const file = path_1.default.join(agentsCatalogDir(project), `${agentId}.md`);
549
+ const roleErrors = validateCustomRoleBody(provider, agentId, body);
550
+ if (roleErrors.length > 0) {
551
+ res.status(400).json({ error: 'invalid_kimi_skill', details: roleErrors });
552
+ return;
553
+ }
554
+ const file = agentFile(project, agentId, provider);
392
555
  if (!fs_1.default.existsSync(file)) {
393
556
  res.status(404).json({ error: 'agent not found' });
394
557
  return;
395
558
  }
396
559
  fs_1.default.writeFileSync(file, body, 'utf8');
397
560
  const maxVersion = db
398
- .prepare(`SELECT COALESCE(MAX(version), 0) AS v FROM agent_versions WHERE agent_name = ?`)
399
- .get(agentId).v;
561
+ .prepare(`SELECT COALESCE(MAX(version), 0) AS v FROM agent_versions
562
+ WHERE provider = ? AND agent_name = ?`)
563
+ .get(provider, agentId).v;
400
564
  const nextVersion = maxVersion + 1;
401
- db.prepare(`INSERT INTO agent_versions (agent_name, version, body, created_at) VALUES (?, ?, ?, ?)`).run(agentId, nextVersion, body, Date.now());
565
+ db.prepare(`INSERT INTO agent_versions (provider, agent_name, version, body, created_at)
566
+ VALUES (?, ?, ?, ?, ?)`).run(provider, agentId, nextVersion, body, Date.now());
402
567
  broadcast({ type: 'agent.changed', projectId: project.id, id: agentId });
403
568
  res.json({ id: agentId, body, version: nextVersion });
404
569
  }
@@ -411,17 +576,24 @@ function createProfilesRouter() {
411
576
  router.delete('/catalog/:agentId', (req, res) => {
412
577
  try {
413
578
  const { project, broadcast } = ctx(req);
579
+ const provider = requestedProvider(req, project);
414
580
  const agentId = req.params.agentId;
415
581
  if (!/^custom-[a-z0-9][a-z0-9-]*$/.test(agentId)) {
416
582
  res.status(403).json({ error: 'only custom-* agents can be deleted' });
417
583
  return;
418
584
  }
419
- const file = path_1.default.join(agentsCatalogDir(project), `${agentId}.md`);
585
+ const file = agentFile(project, agentId, provider);
420
586
  if (!fs_1.default.existsSync(file)) {
421
587
  res.status(404).json({ error: 'agent not found' });
422
588
  return;
423
589
  }
424
590
  fs_1.default.unlinkSync(file);
591
+ try {
592
+ const parent = path_1.default.dirname(file);
593
+ if (parent !== agentsCatalogDir(project, provider) && fs_1.default.readdirSync(parent).length === 0)
594
+ fs_1.default.rmdirSync(parent);
595
+ }
596
+ catch { /* best-effort */ }
425
597
  broadcast({ type: 'agent.changed', projectId: project.id, id: agentId, deleted: true });
426
598
  res.json({ ok: true });
427
599
  }
@@ -436,6 +608,9 @@ function createProfilesRouter() {
436
608
  router.post('/catalog/test', async (req, res) => {
437
609
  try {
438
610
  const { project, db, broadcast } = ctx(req);
611
+ const provider = requestedProvider(req, project, req.body?.provider);
612
+ if (!requireSafeStudioPolicy(res, provider))
613
+ return;
439
614
  const agentId = (req.body?.agentId ?? '').toString().trim() || 'draft';
440
615
  const draftBody = (req.body?.draftBody ?? '').toString();
441
616
  const sampleTask = (req.body?.sampleTask ?? '').toString().trim();
@@ -450,10 +625,12 @@ function createProfilesRouter() {
450
625
  const result = await (0, agent_generator_1.testCustomAgent)(project.path, {
451
626
  draftBody,
452
627
  sampleTask,
628
+ providerId: provider,
453
629
  record: { db, projectId: project.id, surfaceRefId: agentId, broadcast },
454
630
  });
455
- db.prepare(`INSERT INTO agent_tests (agent_name, draft_hash, sample_task_id, tokens, duration_ms, output, created_at)
456
- VALUES (?, ?, ?, ?, ?, ?, ?)`).run(agentId, result.draftHash, null, result.tokens, result.durationMs, result.output, Date.now());
631
+ db.prepare(`INSERT INTO agent_tests
632
+ (provider, agent_name, draft_hash, sample_task_id, tokens, duration_ms, output, created_at)
633
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(provider, agentId, result.draftHash, null, result.tokens, result.durationMs, result.output, Date.now());
457
634
  res.json(result);
458
635
  }
459
636
  catch (err) {
@@ -467,6 +644,9 @@ function createProfilesRouter() {
467
644
  router.post('/catalog/generate', async (req, res) => {
468
645
  try {
469
646
  const { project, db, broadcast } = ctx(req);
647
+ const provider = requestedProvider(req, project, req.body?.provider);
648
+ if (!requireSafeStudioPolicy(res, provider))
649
+ return;
470
650
  const name = (req.body?.name ?? '').toString().trim();
471
651
  const description = (req.body?.description ?? '').toString().trim();
472
652
  if (!/^custom-[a-z0-9][a-z0-9-]*$/.test(name)) {
@@ -480,6 +660,7 @@ function createProfilesRouter() {
480
660
  const draft = await (0, agent_generator_1.generateCustomAgent)(project.path, {
481
661
  name,
482
662
  description,
663
+ providerId: provider,
483
664
  record: { db, projectId: project.id, surfaceRefId: name, broadcast },
484
665
  });
485
666
  res.json({ draft });
@@ -493,7 +674,20 @@ function createProfilesRouter() {
493
674
  router.post('/catalog/:agentId/refine', async (req, res) => {
494
675
  try {
495
676
  const ctxObj = ctx(req);
496
- const { agentRefineManager } = ctxObj;
677
+ const { agentRefineManager, project } = ctxObj;
678
+ const provider = requestedProvider(req, project, req.body?.provider);
679
+ if (!requireSafeStudioPolicy(res, provider))
680
+ return;
681
+ // AgentRefineManager owns the primary provider's native session store.
682
+ // Cross-provider sessions need a provider column/migration before they
683
+ // can be resumed safely; reject instead of silently using the primary.
684
+ if (provider !== (project.provider ?? 'claude')) {
685
+ res.status(409).json({
686
+ error: 'provider_refine_manager_unavailable',
687
+ provider,
688
+ });
689
+ return;
690
+ }
497
691
  const agentId = req.params.agentId;
498
692
  if (!/^custom-[a-z0-9][a-z0-9-]*$/.test(agentId)) {
499
693
  res.status(400).json({ error: 'not_a_custom_agent' });
@@ -528,7 +722,17 @@ function createProfilesRouter() {
528
722
  });
529
723
  router.post('/catalog/:agentId/refine/:refineId/turn', async (req, res) => {
530
724
  try {
531
- const { agentRefineManager, db } = ctx(req);
725
+ const { agentRefineManager, db, project } = ctx(req);
726
+ const provider = requestedProvider(req, project, req.body?.provider);
727
+ if (!requireSafeStudioPolicy(res, provider))
728
+ return;
729
+ if (provider !== (project.provider ?? 'claude')) {
730
+ res.status(409).json({
731
+ error: 'provider_refine_manager_unavailable',
732
+ provider,
733
+ });
734
+ return;
735
+ }
532
736
  const refineId = req.params.refineId;
533
737
  const instruction = (req.body?.instruction ?? '').toString().trim();
534
738
  if (!instruction) {
@@ -661,12 +865,13 @@ function createProfilesRouter() {
661
865
  // GET /api/projects/:projectId/profiles/catalog/:agentId/versions
662
866
  router.get('/catalog/:agentId/versions', (req, res) => {
663
867
  try {
664
- const { db } = ctx(req);
868
+ const { db, project } = ctx(req);
869
+ const provider = requestedProvider(req, project);
665
870
  const agentId = req.params.agentId;
666
871
  const rows = db
667
872
  .prepare(`SELECT version, body, created_at AS createdAt FROM agent_versions
668
- WHERE agent_name = ? ORDER BY version DESC`)
669
- .all(agentId);
873
+ WHERE provider = ? AND agent_name = ? ORDER BY version DESC`)
874
+ .all(provider, agentId);
670
875
  res.json({ versions: rows });
671
876
  }
672
877
  catch (err) {
@@ -677,7 +882,12 @@ function createProfilesRouter() {
677
882
  router.get('/', (req, res) => {
678
883
  try {
679
884
  const { project } = ctx(req);
680
- res.json({ profiles: (0, profile_manager_1.listProfiles)(specRoot(project)) });
885
+ const provider = requestedProvider(req, project);
886
+ const primary = project.provider ?? 'claude';
887
+ res.json({
888
+ profiles: (0, profile_manager_1.listProfiles)(specRoot(project), primary)
889
+ .filter((profile) => profile.provider === provider),
890
+ });
681
891
  }
682
892
  catch (err) {
683
893
  handleError(res, err);
@@ -687,8 +897,9 @@ function createProfilesRouter() {
687
897
  router.get('/resolve', (req, res) => {
688
898
  try {
689
899
  const { project } = ctx(req);
900
+ const provider = requestedProvider(req, project);
690
901
  const explicit = typeof req.query.profile === 'string' ? req.query.profile : undefined;
691
- const resolved = (0, profile_manager_1.resolveProfile)(specRoot(project), explicit, project.provider ?? 'claude');
902
+ const resolved = (0, profile_manager_1.resolveProfile)(specRoot(project), explicit, provider);
692
903
  if (!resolved) {
693
904
  res.json({ resolved: null });
694
905
  return;
@@ -703,8 +914,15 @@ function createProfilesRouter() {
703
914
  router.post('/', (req, res) => {
704
915
  try {
705
916
  const { project, broadcast } = ctx(req);
706
- const body = req.body;
707
- (0, profile_manager_1.createProfile)(specRoot(project), body, project.provider ?? 'claude');
917
+ const input = req.body;
918
+ const provider = requestedProvider(req, project, input?.provider);
919
+ const body = {
920
+ ...input,
921
+ ...(input?.provider || provider !== 'claude' || installedProviders(project).length > 1
922
+ ? { provider }
923
+ : {}),
924
+ };
925
+ (0, profile_manager_1.createProfile)(specRoot(project), body, provider);
708
926
  broadcast({ type: 'profile.changed', projectId: project.id, name: body.name });
709
927
  res.status(201).json({ profile: body });
710
928
  }
@@ -716,12 +934,13 @@ function createProfilesRouter() {
716
934
  router.post('/:name/duplicate', (req, res) => {
717
935
  try {
718
936
  const { project, broadcast } = ctx(req);
937
+ const provider = requestedProvider(req, project, req.body?.provider);
719
938
  const newName = (req.body?.name ?? '').toString();
720
939
  if (!newName) {
721
940
  res.status(400).json({ error: "body field 'name' is required" });
722
941
  return;
723
942
  }
724
- const copy = (0, profile_manager_1.duplicateProfile)(specRoot(project), req.params.name, newName, project.provider ?? 'claude');
943
+ const copy = (0, profile_manager_1.duplicateProfile)(specRoot(project), req.params.name, newName, provider);
725
944
  broadcast({ type: 'profile.changed', projectId: project.id, name: newName });
726
945
  res.status(201).json({ profile: copy });
727
946
  }
@@ -733,12 +952,13 @@ function createProfilesRouter() {
733
952
  router.post('/:name/rename', (req, res) => {
734
953
  try {
735
954
  const { project, broadcast } = ctx(req);
955
+ const provider = requestedProvider(req, project, req.body?.provider);
736
956
  const newName = (req.body?.name ?? '').toString();
737
957
  if (!newName) {
738
958
  res.status(400).json({ error: "body field 'name' is required" });
739
959
  return;
740
960
  }
741
- const renamed = (0, profile_manager_1.renameProfile)(specRoot(project), req.params.name, newName, project.provider ?? 'claude');
961
+ const renamed = (0, profile_manager_1.renameProfile)(specRoot(project), req.params.name, newName, provider);
742
962
  broadcast({ type: 'profile.changed', projectId: project.id, name: newName });
743
963
  res.json({ profile: renamed });
744
964
  }
@@ -750,10 +970,11 @@ function createProfilesRouter() {
750
970
  router.get('/:name', (req, res) => {
751
971
  try {
752
972
  const { project } = ctx(req);
973
+ const provider = requestedProvider(req, project);
753
974
  // Non-validating read so a profile that drifted invalid against the current
754
975
  // catalog can still be opened + repaired in the editor (getProfile threw and
755
976
  // locked it out). The body + validation errors are both returned.
756
- const { profile, valid, errors } = (0, profile_manager_1.getProfileRaw)(specRoot(project), req.params.name, project.provider ?? 'claude');
977
+ const { profile, valid, errors } = (0, profile_manager_1.getProfileRaw)(specRoot(project), req.params.name, provider);
757
978
  res.json({ profile, valid, validationErrors: errors });
758
979
  }
759
980
  catch (err) {
@@ -764,12 +985,19 @@ function createProfilesRouter() {
764
985
  router.patch('/:name', (req, res) => {
765
986
  try {
766
987
  const { project, broadcast } = ctx(req);
767
- const body = req.body;
988
+ const input = req.body;
989
+ const provider = requestedProvider(req, project, input?.provider);
990
+ const body = {
991
+ ...input,
992
+ ...(input?.provider || provider !== 'claude' || installedProviders(project).length > 1
993
+ ? { provider }
994
+ : {}),
995
+ };
768
996
  if (body.name !== req.params.name) {
769
997
  res.status(400).json({ error: "body.name must match path parameter (use /rename to change name)" });
770
998
  return;
771
999
  }
772
- (0, profile_manager_1.updateProfile)(specRoot(project), body, project.provider ?? 'claude');
1000
+ (0, profile_manager_1.updateProfile)(specRoot(project), body, provider);
773
1001
  broadcast({ type: 'profile.changed', projectId: project.id, name: body.name });
774
1002
  res.json({ profile: body });
775
1003
  }
@@ -781,7 +1009,8 @@ function createProfilesRouter() {
781
1009
  router.delete('/:name', (req, res) => {
782
1010
  try {
783
1011
  const { project, broadcast } = ctx(req);
784
- (0, profile_manager_1.deleteProfile)(specRoot(project), req.params.name);
1012
+ const provider = requestedProvider(req, project);
1013
+ (0, profile_manager_1.deleteProfile)(specRoot(project), req.params.name, provider);
785
1014
  broadcast({ type: 'profile.changed', projectId: project.id, name: req.params.name, deleted: true });
786
1015
  res.json({ ok: true });
787
1016
  }