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
@@ -803,7 +803,7 @@ class ProjectRegistry {
803
803
  // an `ai_invocations` row (surface='setup'). Returns null until the project
804
804
  // context exists, in which case nothing is recorded (best-effort).
805
805
  (pid) => this._contexts.get(pid)?.db ?? null);
806
- const proposalManager = new proposal_manager_1.ProposalManager(boundBroadcast, db, project.path, project.id);
806
+ const proposalManager = new proposal_manager_1.ProposalManager(boundBroadcast, db, project.path, project.id, project.provider ?? 'claude', project.slug);
807
807
  const agentRefineManager = new agent_refine_manager_1.AgentRefineManager(boundBroadcast, db, project.path, project.id, project.provider ?? 'claude');
808
808
  // Retention prune: drop stale/abandoned refine sessions on project load.
809
809
  try {
@@ -812,7 +812,7 @@ class ProjectRegistry {
812
812
  catch (err) {
813
813
  console.error('[project-registry] prune refine sessions failed:', err);
814
814
  }
815
- const specLauncherManager = new spec_launcher_manager_1.SpecLauncherManager(boundBroadcast, project.path, db, project.id);
815
+ const specLauncherManager = new spec_launcher_manager_1.SpecLauncherManager(boundBroadcast, project.path, db, project.id, project.provider ?? 'claude', project.slug);
816
816
  // FileSummaryManager — code-explorer. The class is constructed for every
817
817
  // project regardless of the feature flag; the router 404s when the flag
818
818
  // is off, so no spawn can occur. Budget reader queries `ai_invocations`
@@ -3,6 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.registerChatRoutes = registerChatRoutes;
4
4
  const ids_1 = require("./ids");
5
5
  const db_1 = require("./db");
6
+ const providers_1 = require("./providers");
7
+ const runtime_1 = require("./providers/runtime");
6
8
  const context_scope_1 = require("./context-scope");
7
9
  const spec_models_1 = require("./spec-models");
8
10
  const provider_selection_1 = require("./provider-selection");
@@ -46,6 +48,14 @@ function registerChatRoutes(deps) {
46
48
  }
47
49
  const rawKind = req.body?.kind;
48
50
  const kind = rawKind === 'explore' ? 'explore' : rawKind === 'milestone' ? 'milestone' : 'sidebar';
51
+ if (kind === 'milestone' && !(0, runtime_1.supportsToolPolicy)((0, providers_1.getAdapter)(provider), 'read-only')) {
52
+ res.status(409).json({
53
+ error: 'provider_tool_policy_unsupported',
54
+ provider,
55
+ requiredPolicy: 'read-only',
56
+ });
57
+ return;
58
+ }
49
59
  const id = (0, ids_1.newId)();
50
60
  const rawScope = req.body?.contextScope;
51
61
  if (rawScope !== undefined && kind !== 'explore') {
@@ -67,11 +77,9 @@ function registerChatRoutes(deps) {
67
77
  let scope;
68
78
  if (kind === 'explore') {
69
79
  const fallback = (0, context_scope_1.getLastContextScope)(db, 'explore');
70
- // Defence-in-depth: SMASH / Contract Layer is Claude-only. Strip
71
- // contractRefine from the scope when the conversation's resolved provider
72
- // is non-Claude so no downstream code (Contract Refine Runner, SMASH
73
- // eligibility) ever sees a mismatched flag.
74
- const safeRawScope = provider !== 'claude' && rawScope != null
80
+ // Defence-in-depth: retain Contract Refine only for providers that
81
+ // advertise the structured-action contract.
82
+ const safeRawScope = (0, providers_1.getAdapter)(provider).capabilities.structuredActions !== true && rawScope != null
75
83
  ? { ...rawScope, contractRefine: false }
76
84
  : rawScope;
77
85
  scope = (0, context_scope_1.normalizeContextScope)(safeRawScope ?? fallback, fallback);
@@ -86,7 +94,7 @@ function registerChatRoutes(deps) {
86
94
  res.status(201).json({ conversation });
87
95
  });
88
96
  router.get('/:projectId/chat/conversations/:id', (req, res) => {
89
- const { db } = ctx(req);
97
+ const { db, project } = ctx(req);
90
98
  const conversation = (0, db_1.getConversation)(db, req.params.id);
91
99
  if (!conversation) {
92
100
  res.status(404).json({ error: 'Conversation not found' });
@@ -128,7 +136,7 @@ function registerChatRoutes(deps) {
128
136
  res.json({ ok: true });
129
137
  });
130
138
  router.patch('/:projectId/chat/conversations/:id', (req, res) => {
131
- const { db } = ctx(req);
139
+ const { db, project } = ctx(req);
132
140
  const conversation = (0, db_1.getConversation)(db, req.params.id);
133
141
  if (!conversation) {
134
142
  res.status(404).json({ error: 'Conversation not found' });
@@ -146,7 +154,8 @@ function registerChatRoutes(deps) {
146
154
  if (model !== undefined) {
147
155
  // Validate against the conversation's own provider so a bad value can't be
148
156
  // persisted and break the next turn's spawn. (Mirrors the POST handler.)
149
- const convProvider = conversation.provider ?? 'claude';
157
+ const convProvider = conversation.provider ??
158
+ (project.provider ?? 'claude');
150
159
  if (typeof model !== 'string' || !(0, spec_models_1.isValidModelForProvider)(model, convProvider)) {
151
160
  res.status(400).json({
152
161
  error: `Invalid model "${String(model)}" for provider "${convProvider}"`,
@@ -13,6 +13,7 @@ exports.deriveFallbackShortSummary = deriveFallbackShortSummary;
13
13
  exports.lightlyStructurePrompt = lightlyStructurePrompt;
14
14
  exports.formatDescriptionWithCriteria = formatDescriptionWithCriteria;
15
15
  exports.resolveDefaultSpecModel = resolveDefaultSpecModel;
16
+ exports.readAgentModelSelection = readAgentModelSelection;
16
17
  exports.readAgentModels = readAgentModels;
17
18
  exports.applyModelConfig = applyModelConfig;
18
19
  exports.serializeInstallConfigYaml = serializeInstallConfigYaml;
@@ -31,6 +32,11 @@ exports.TERMINAL_PANEL_ENABLED = TERMINAL_PANEL_ENABLED;
31
32
  */
32
33
  function serializeInstallConfigYaml(config) {
33
34
  const c = config;
35
+ const requestedProvider = typeof c.provider === 'string' ? c.provider : '';
36
+ const provider = (0, spec_models_1.getProviderDefault)(requestedProvider)
37
+ ? requestedProvider
38
+ : 'claude';
39
+ const defaultModel = (0, spec_models_1.getProviderDefault)(provider) || (0, spec_models_1.getProviderDefault)('claude');
34
40
  const overrides = c.models?.overrides ?? {};
35
41
  const overridesEntries = Object.entries(overrides);
36
42
  const overridesYaml = overridesEntries.length > 0
@@ -40,14 +46,14 @@ function serializeInstallConfigYaml(config) {
40
46
  '# specrails install config — generated by Specrails',
41
47
  `# Re-run: npx ${core_package_1.CORE_PACKAGE_SPEC} init to regenerate`,
42
48
  `version: ${c.version ?? 1}`,
43
- `provider: ${c.provider ?? 'claude'}`,
49
+ `provider: ${provider}`,
44
50
  `tier: ${c.tier ?? 'quick'}`,
45
51
  `agents:`,
46
52
  ` selected: [${(c.agents?.selected ?? []).join(', ')}]`,
47
53
  ` excluded: [${(c.agents?.excluded ?? []).join(', ')}]`,
48
54
  `models:`,
49
55
  ` preset: ${c.models?.preset ?? 'balanced'}`,
50
- ` defaults: { model: ${c.models?.defaults?.model ?? 'sonnet'} }`,
56
+ ` defaults: { model: ${c.models?.defaults?.model ?? defaultModel} }`,
51
57
  ` overrides:${overridesYaml}`,
52
58
  `agent_teams: ${c.agent_teams ?? false}`,
53
59
  '',
@@ -57,6 +63,13 @@ function serializeInstallConfigYaml(config) {
57
63
  // ─── Agent model helpers ──────────────────────────────────────────────────────
58
64
  const VALID_MODEL_ALIASES = ['sonnet', 'fable', 'opus', 'haiku'];
59
65
  exports.VALID_MODEL_ALIASES = VALID_MODEL_ALIASES;
66
+ function projectExecutionRoot(project) {
67
+ const exec = (0, workspace_resolution_1.resolveProjectExecution)({ slug: project.slug, path: project.path });
68
+ return exec.relocated && exec.workspaceDir ? exec.workspaceDir : project.path;
69
+ }
70
+ function isKimiAgentModelProject(project) {
71
+ return project.provider === 'kimi';
72
+ }
60
73
  /**
61
74
  * Relocate-artifacts: where the agent `.claude/agents/*.md` files live. When the
62
75
  * project is relocated AND the workspace is populated by core, the agents are
@@ -64,15 +77,84 @@ exports.VALID_MODEL_ALIASES = VALID_MODEL_ALIASES;
64
77
  * router uses for profiles). Legacy ⇒ `<project>/.claude/agents` (byte-identical).
65
78
  */
66
79
  function resolveAgentsDir(project) {
67
- const exec = (0, workspace_resolution_1.resolveProjectExecution)({ slug: project.slug, path: project.path });
68
- const root = exec.relocated && exec.workspaceDir ? exec.workspaceDir : project.path;
69
- return path_1.default.join(root, '.claude', 'agents');
80
+ return path_1.default.join(projectExecutionRoot(project), '.claude', 'agents');
81
+ }
82
+ function resolveKimiSkillsDir(project) {
83
+ return path_1.default.join(projectExecutionRoot(project), '.kimi-code', 'skills');
84
+ }
85
+ function readAgentModelSelection(project) {
86
+ const provider = (project.provider ?? 'claude');
87
+ const fallback = (0, spec_models_1.getProviderDefault)(provider);
88
+ const configPath = (0, install_config_path_1.installConfigPath)(project);
89
+ if (!fs_1.default.existsSync(configPath))
90
+ return { defaultModel: fallback, overrides: {} };
91
+ let configText;
92
+ try {
93
+ configText = fs_1.default.readFileSync(configPath, 'utf-8');
94
+ }
95
+ catch {
96
+ return { defaultModel: fallback, overrides: {} };
97
+ }
98
+ const defaultsMatch = configText.match(/defaults:\s*\{\s*model:\s*(\S+?)\s*\}/);
99
+ const configuredDefault = defaultsMatch?.[1];
100
+ const defaultModel = configuredDefault && (0, spec_models_1.isValidModelForProvider)(configuredDefault, provider)
101
+ ? configuredDefault
102
+ : fallback;
103
+ const overrides = {};
104
+ const overridesBlockMatch = configText.match(/overrides:([\s\S]*?)(?:\n\S|$)/);
105
+ if (overridesBlockMatch) {
106
+ const overrideLines = overridesBlockMatch[1].match(/^ {2,}(\S+):\s*(\S+)/gm) ?? [];
107
+ for (const line of overrideLines) {
108
+ const match = line.match(/^\s+(\S+):\s*(\S+)/);
109
+ if (match
110
+ && (0, spec_models_1.isValidModelForProvider)(match[2], provider)) {
111
+ overrides[match[1]] = match[2];
112
+ }
113
+ }
114
+ }
115
+ return { defaultModel, overrides };
116
+ }
117
+ function readKimiAgentModels(project) {
118
+ const skillsDir = resolveKimiSkillsDir(project);
119
+ if (!fs_1.default.existsSync(skillsDir))
120
+ return [];
121
+ let entries;
122
+ try {
123
+ entries = fs_1.default.readdirSync(skillsDir, { withFileTypes: true });
124
+ }
125
+ catch {
126
+ return [];
127
+ }
128
+ const { defaultModel, overrides } = readAgentModelSelection(project);
129
+ const agents = [];
130
+ for (const entry of entries) {
131
+ // Kimi commands and OpenSpec skills share this directory. Only direct
132
+ // provider-native rail roles participate in per-agent model overrides.
133
+ if (!entry.isDirectory()
134
+ || (!entry.name.startsWith('sr-') && !entry.name.startsWith('custom-')))
135
+ continue;
136
+ const skillPath = path_1.default.join(skillsDir, entry.name, 'SKILL.md');
137
+ try {
138
+ if (!fs_1.default.statSync(skillPath).isFile())
139
+ continue;
140
+ agents.push({
141
+ name: entry.name,
142
+ model: overrides[entry.name] ?? defaultModel,
143
+ });
144
+ }
145
+ catch {
146
+ // Skip incomplete or unreadable role directories.
147
+ }
148
+ }
149
+ return agents;
70
150
  }
71
151
  /**
72
152
  * Read installed agents from `.claude/agents/*.md` (top-level only, no subdirs).
73
153
  * Extracts the `model:` field from YAML frontmatter.
74
154
  */
75
155
  function readAgentModels(project) {
156
+ if (isKimiAgentModelProject(project))
157
+ return readKimiAgentModels(project);
76
158
  const agentsDir = resolveAgentsDir(project);
77
159
  if (!fs_1.default.existsSync(agentsDir))
78
160
  return [];
@@ -115,6 +197,12 @@ function readAgentModels(project) {
115
197
  * defaults/overrides. No-op if the config file does not exist.
116
198
  */
117
199
  function applyModelConfig(project) {
200
+ // Kimi SKILL.md frontmatter has no per-role `model:` field. Its selected
201
+ // default/overrides live in install-config.yaml and are applied by the
202
+ // runtime when the role is invoked. Never inject Claude agent syntax into a
203
+ // Kimi skill: readAgentModels projects the config over the direct roles.
204
+ if (isKimiAgentModelProject(project))
205
+ return;
118
206
  const configPath = (0, install_config_path_1.installConfigPath)(project);
119
207
  if (!fs_1.default.existsSync(configPath))
120
208
  return;
@@ -10,6 +10,7 @@ const db_1 = require("./db");
10
10
  const queue_manager_1 = require("./queue-manager");
11
11
  const feature_flags_1 = require("./feature-flags");
12
12
  const types_1 = require("./types");
13
+ const providers_1 = require("./providers");
13
14
  const hooks_1 = require("./hooks");
14
15
  const explore_smash_1 = require("./explore-smash");
15
16
  const job_spawn_idempotency_1 = require("./job-spawn-idempotency");
@@ -39,8 +40,38 @@ function registerJobsRoutes(deps) {
39
40
  const { router, registry, ctx, ticketPath } = deps;
40
41
  // ─── Queue / Spawn routes ────────────────────────────────────────────────────
41
42
  router.post('/:projectId/spawn', (req, res) => {
42
- const { command, priority, dependsOnJobId, pipelineId, profileName, aiEngine } = req.body ?? {};
43
- if (!command || typeof command !== 'string' || !command.trim()) {
43
+ const { command, priority, dependsOnJobId, pipelineId, profileName, aiEngine, model, rerunOfJobId, } = req.body ?? {};
44
+ if (rerunOfJobId !== undefined
45
+ && (typeof rerunOfJobId !== 'string' || !/^[A-Za-z0-9._:-]{1,128}$/.test(rerunOfJobId))) {
46
+ res.status(400).json({ error: 'rerunOfJobId must be 1-128 URL-safe characters' });
47
+ return;
48
+ }
49
+ const c = ctx(req);
50
+ const rerunSource = typeof rerunOfJobId === 'string'
51
+ ? (0, db_1.getJob)(c.db, rerunOfJobId)
52
+ : undefined;
53
+ if (typeof rerunOfJobId === 'string' && !rerunSource) {
54
+ res.status(404).json({ error: 'Rerun source job not found' });
55
+ return;
56
+ }
57
+ if (rerunSource
58
+ && rerunSource.status !== 'completed'
59
+ && rerunSource.status !== 'failed') {
60
+ res.status(409).json({ error: 'Only completed or failed jobs can be rerun' });
61
+ return;
62
+ }
63
+ // A rerun is server-owned: never trust duplicated command/provider/model/
64
+ // profile metadata from the client. The exact command retains ticket
65
+ // identity, while the persisted job + profile snapshot retain execution
66
+ // identity. Jobs do not persist reasoning effort or launch-surface origin,
67
+ // so this route deliberately does not manufacture either value.
68
+ const effectiveCommand = rerunSource?.command ?? command;
69
+ const effectiveAiEngine = rerunSource?.provider ?? (rerunSource ? undefined : aiEngine);
70
+ const requestedModel = rerunSource?.model ?? (rerunSource ? undefined : model);
71
+ const requestedProfileName = rerunSource
72
+ ? (rerunSource.profile_name ?? null)
73
+ : profileName;
74
+ if (!effectiveCommand || typeof effectiveCommand !== 'string' || !effectiveCommand.trim()) {
44
75
  res.status(400).json({ error: 'command is required' });
45
76
  return;
46
77
  }
@@ -57,8 +88,8 @@ function registerJobsRoutes(deps) {
57
88
  ? dependsOnJobId.trim()
58
89
  : undefined;
59
90
  // profileName accepts: undefined (default resolution), null (force legacy), string (explicit)
60
- const normalizedProfileName = profileName === null ? null
61
- : typeof profileName === 'string' && profileName.trim() ? profileName.trim()
91
+ const normalizedProfileName = requestedProfileName === null ? null
92
+ : typeof requestedProfileName === 'string' && requestedProfileName.trim() ? requestedProfileName.trim()
62
93
  : undefined;
63
94
  const idempotencyKey = req.get('Idempotency-Key');
64
95
  if (idempotencyKey !== undefined && !/^[A-Za-z0-9._:-]{1,128}$/.test(idempotencyKey)) {
@@ -67,21 +98,46 @@ function registerJobsRoutes(deps) {
67
98
  }
68
99
  // aiEngine: optional per-job provider override; must be installed on the
69
100
  // project. Omitting it runs on the project's primary provider.
70
- const engineCheck = (0, provider_selection_1.validateRequestedProvider)(ctx(req).project, aiEngine);
101
+ const engineCheck = (0, provider_selection_1.validateRequestedProvider)(c.project, effectiveAiEngine);
71
102
  if (!engineCheck.ok) {
72
103
  res.status(400).json({ error: engineCheck.error });
73
104
  return;
74
105
  }
106
+ let normalizedModel;
107
+ if (requestedModel !== undefined && requestedModel !== null) {
108
+ if (typeof requestedModel !== 'string'
109
+ || !(0, spec_models_1.isValidModelForProvider)(requestedModel, engineCheck.provider)) {
110
+ // Some older non-alias providers persisted the provider-reported full
111
+ // model ID rather than the launch alias. Preserve their historical
112
+ // rerun behaviour (provider default) while failing closed for adapters
113
+ // that advertise exact custom aliases such as Kimi.
114
+ if (rerunSource
115
+ && (0, providers_1.getAdapter)(engineCheck.provider).capabilities.customModelAliases !== true) {
116
+ normalizedModel = undefined;
117
+ }
118
+ else {
119
+ res.status(400).json({
120
+ error: `model is not valid for provider "${engineCheck.provider}"`,
121
+ allowed: (0, spec_models_1.getModelsForProvider)(engineCheck.provider),
122
+ });
123
+ return;
124
+ }
125
+ }
126
+ else {
127
+ normalizedModel = requestedModel;
128
+ }
129
+ }
75
130
  try {
76
- const c = ctx(req);
77
131
  const normalizedPriority = priority ?? 'normal';
78
132
  const spawnFingerprint = idempotencyKey ? (0, job_spawn_idempotency_1.fingerprintJobSpawn)({
79
- command,
133
+ command: effectiveCommand,
80
134
  priority: normalizedPriority,
81
135
  dependsOnJobId: normalizedDependsOnJobId ?? null,
82
136
  pipelineId: pipelineId || null,
83
137
  profileName: normalizedProfileName === undefined ? '__project_default__' : normalizedProfileName,
84
- provider: aiEngine ? engineCheck.provider : null,
138
+ provider: effectiveAiEngine ? engineCheck.provider : null,
139
+ model: normalizedModel ?? null,
140
+ rerunOfJobId: rerunOfJobId ?? null,
85
141
  }) : null;
86
142
  if (idempotencyKey && spawnFingerprint) {
87
143
  const existingJobId = (0, job_spawn_idempotency_1.findIdempotentJob)(c.db, idempotencyKey, spawnFingerprint);
@@ -101,14 +157,15 @@ function registerJobsRoutes(deps) {
101
157
  ...(normalizedProfileName !== undefined
102
158
  ? { profileName: normalizedProfileName }
103
159
  : {}),
104
- provider: aiEngine ? engineCheck.provider : undefined,
160
+ provider: effectiveAiEngine ? engineCheck.provider : undefined,
161
+ ...(normalizedModel ? { model: normalizedModel } : {}),
105
162
  };
106
163
  const job = idempotencyKey && spawnFingerprint && preparedJobId
107
- ? c.queueManager.enqueue(command, normalizedPriority, enqueueOptions, {
164
+ ? c.queueManager.enqueue(effectiveCommand, normalizedPriority, enqueueOptions, {
108
165
  jobId: preparedJobId,
109
166
  commit: (db) => (0, job_spawn_idempotency_1.claimIdempotentJob)(db, idempotencyKey, spawnFingerprint, preparedJobId),
110
167
  })
111
- : c.queueManager.enqueue(command, normalizedPriority, enqueueOptions);
168
+ : c.queueManager.enqueue(effectiveCommand, normalizedPriority, enqueueOptions);
112
169
  const position = job.queuePosition ?? 0;
113
170
  res.status(202).json({ jobId: job.id, position });
114
171
  }
@@ -280,7 +337,14 @@ function registerJobsRoutes(deps) {
280
337
  const provider = (0, provider_selection_1.resolveProvider)(project, typeof req.query.provider === 'string' ? req.query.provider : undefined);
281
338
  const model = (0, project_router_helpers_1.resolveDefaultSpecModel)({ projectPath: project.path, slug: project.slug, provider });
282
339
  const allowed = (0, spec_models_1.getModelsForProvider)(provider);
283
- res.json({ model, provider, allowed, providers: project.providers });
340
+ const adapter = (0, providers_1.getAdapter)(provider);
341
+ res.json({
342
+ model,
343
+ provider,
344
+ allowed,
345
+ providers: project.providers,
346
+ customModelAliases: adapter.capabilities.customModelAliases === true,
347
+ });
284
348
  });
285
349
  // Cancellation is idempotent and intentionally separate from history
286
350
  // deletion. A stale UI request that races with natural completion must never
@@ -11,7 +11,6 @@ const spec_models_1 = require("./spec-models");
11
11
  const workspace_resolution_1 = require("./workspace-resolution");
12
12
  const loop_command_catalog_1 = require("./loop-command-catalog");
13
13
  const ids_1 = require("./ids");
14
- const VALID_REASONING_EFFORTS = new Set(['low', 'medium', 'high']);
15
14
  function registerLoopRunRoutes(deps) {
16
15
  const { router, ctx } = deps;
17
16
  // GET a single loop run's live/terminal state. Backs the companion's running
@@ -27,7 +26,19 @@ function registerLoopRunRoutes(deps) {
27
26
  res.status(404).json({ error: 'Loop run not found' });
28
27
  return;
29
28
  }
30
- res.json({ loopRun: run });
29
+ const usageAvailable = !run.provider
30
+ || !(0, providers_1.hasAdapter)(run.provider)
31
+ || (0, providers_1.getAdapter)(run.provider).capabilities.reportsUsage !== false;
32
+ res.json({
33
+ loopRun: usageAvailable
34
+ ? { ...run, usage_available: true }
35
+ : {
36
+ ...run,
37
+ total_cost_usd: null,
38
+ total_tokens: null,
39
+ usage_available: false,
40
+ },
41
+ });
31
42
  });
32
43
  router.post('/:projectId/loop-runs', (req, res) => {
33
44
  if (!(0, feature_flags_1.isLoopsEnabled)()) {
@@ -50,27 +61,31 @@ function registerLoopRunRoutes(deps) {
50
61
  res.status(400).json({ error: 'Loop must be published before it can run' });
51
62
  return;
52
63
  }
53
- let effort;
54
- if (reasoning_effort !== undefined && reasoning_effort !== null) {
55
- if (typeof reasoning_effort !== 'string' || !VALID_REASONING_EFFORTS.has(reasoning_effort)) {
56
- res.status(400).json({ error: 'reasoning_effort must be one of: low, medium, high' });
57
- return;
58
- }
59
- effort = reasoning_effort;
60
- }
61
64
  const check = (0, provider_selection_1.validateRequestedProvider)(c.project, aiEngine ?? providerAlias);
62
65
  if (!check.ok) {
63
66
  res.status(400).json({ error: check.error });
64
67
  return;
65
68
  }
66
69
  const provider = check.provider;
67
- // claude-only guard (e.g. a loop that uses {{cmd:freestyle}}).
70
+ const adapter = (0, providers_1.getAdapter)(provider);
71
+ if (loop.graph.nodes.some((node) => node.type === 'decider')
72
+ && !(0, providers_1.supportsToolPolicy)(adapter, 'read-only')) {
73
+ res.status(409).json({
74
+ code: 'provider_tool_policy_unsupported',
75
+ provider,
76
+ requiredPolicy: 'read-only',
77
+ error: `Provider '${provider}' cannot run Loop Deciders because its headless CLI ` +
78
+ 'does not enforce a read-only tool policy.',
79
+ });
80
+ return;
81
+ }
82
+ // Provider-capability guard (e.g. a loop that uses {{cmd:freestyle}}).
68
83
  const promptsText = loop.graph.nodes
69
84
  .filter((n) => n.type === 'ai-step')
70
85
  .map((n) => String(n.data?.prompt ?? ''))
71
86
  .join('\n');
72
- if ((0, loop_command_catalog_1.referencesClaudeOnlyCommand)(promptsText) && provider !== 'claude') {
73
- res.status(400).json({ error: 'This loop uses a Claude-only command and requires the Claude provider' });
87
+ if ((0, loop_command_catalog_1.referencesUnsupportedProviderCommand)(promptsText, provider)) {
88
+ res.status(400).json({ error: `This loop uses a command unsupported by provider '${provider}'` });
74
89
  return;
75
90
  }
76
91
  // Optional explicit model — validated against the chosen provider's catalog
@@ -84,7 +99,20 @@ function registerLoopRunRoutes(deps) {
84
99
  model = requestedModel;
85
100
  }
86
101
  else {
87
- model = (0, providers_1.getAdapter)(provider).defaultModel();
102
+ model = adapter.defaultModel();
103
+ }
104
+ let effort;
105
+ if (reasoning_effort !== undefined && reasoning_effort !== null) {
106
+ const allowed = (0, providers_1.reasoningEffortsForModel)(adapter, model);
107
+ if (typeof reasoning_effort !== 'string' ||
108
+ !allowed.includes(reasoning_effort)) {
109
+ res.status(400).json({
110
+ error: `reasoning_effort is not valid for provider "${provider}" and model "${model}"`,
111
+ allowed,
112
+ });
113
+ return;
114
+ }
115
+ effort = reasoning_effort;
88
116
  }
89
117
  const exec = (0, workspace_resolution_1.resolveProjectExecution)({ slug: c.project.slug, path: c.project.path });
90
118
  const runId = (0, ids_1.newId)();
@@ -14,6 +14,7 @@ const integration_branch_1 = require("./integration-branch");
14
14
  const worktree_manager_1 = require("./worktree-manager");
15
15
  const context_budget_1 = require("./context-budget");
16
16
  const context_scope_1 = require("./context-scope");
17
+ const spec_models_1 = require("./spec-models");
17
18
  const terminal_settings_1 = require("./terminal-settings");
18
19
  const terminal_marks_store_1 = require("./terminal-marks-store");
19
20
  const project_router_helpers_1 = require("./project-router-helpers");
@@ -161,10 +162,14 @@ function registerSettingsRoutes(deps) {
161
162
  router.patch('/:projectId/agent-models', (req, res) => {
162
163
  const { project } = ctx(req);
163
164
  const { defaultModel, overrides } = req.body ?? {};
165
+ const provider = (project.provider ?? 'claude');
166
+ const allowedModels = (0, spec_models_1.getModelsForProvider)(provider).map((model) => model.value);
164
167
  // Validate defaultModel if provided
165
168
  if (defaultModel !== undefined) {
166
- if (typeof defaultModel !== 'string' || !project_router_helpers_1.VALID_MODEL_ALIASES.includes(defaultModel)) {
167
- res.status(400).json({ error: `Invalid model alias. Must be one of: ${project_router_helpers_1.VALID_MODEL_ALIASES.join(', ')}` });
169
+ if (!(0, spec_models_1.isValidModelForProvider)(defaultModel, provider)) {
170
+ res.status(400).json({
171
+ error: `Invalid model alias for provider "${provider}". Catalog: ${allowedModels.join(', ')}`,
172
+ });
168
173
  return;
169
174
  }
170
175
  }
@@ -175,8 +180,11 @@ function registerSettingsRoutes(deps) {
175
180
  return;
176
181
  }
177
182
  for (const [agentName, modelValue] of Object.entries(overrides)) {
178
- if (typeof modelValue !== 'string' || !project_router_helpers_1.VALID_MODEL_ALIASES.includes(modelValue)) {
179
- res.status(400).json({ error: `Invalid model alias for agent "${agentName}". Must be one of: ${project_router_helpers_1.VALID_MODEL_ALIASES.join(', ')}` });
183
+ if (!(0, spec_models_1.isValidModelForProvider)(modelValue, provider)) {
184
+ res.status(400).json({
185
+ error: `Invalid model alias for agent "${agentName}" and provider "${provider}". ` +
186
+ `Catalog: ${allowedModels.join(', ')}`,
187
+ });
180
188
  return;
181
189
  }
182
190
  }
@@ -185,12 +193,17 @@ function registerSettingsRoutes(deps) {
185
193
  // NEVER `<project>/.specrails` (which would leak into the user's repo).
186
194
  const configPath = (0, install_config_path_1.installConfigPath)(project);
187
195
  // Read existing config or build default shape
196
+ const providerDefault = (0, spec_models_1.getProviderDefault)(provider) || (0, spec_models_1.getProviderDefault)('claude');
188
197
  let existingConfig = {
189
198
  version: 1,
190
- provider: 'claude',
199
+ provider,
191
200
  tier: 'quick',
192
201
  agents: { selected: [], excluded: [] },
193
- models: { preset: 'balanced', defaults: { model: 'sonnet' }, overrides: {} },
202
+ models: {
203
+ preset: 'balanced',
204
+ defaults: { model: providerDefault },
205
+ overrides: {},
206
+ },
194
207
  agent_teams: false,
195
208
  };
196
209
  if (fs_1.default.existsSync(configPath)) {
@@ -198,9 +211,9 @@ function registerSettingsRoutes(deps) {
198
211
  const text = fs_1.default.readFileSync(configPath, 'utf-8');
199
212
  // Parse fields we care about from the existing config text
200
213
  const versionMatch = text.match(/^version:\s*(\d+)/m);
201
- const providerMatch = text.match(/^provider:\s*(\S+)/m);
202
214
  const tierMatch = text.match(/^tier:\s*(\S+)/m);
203
215
  const presetMatch = text.match(/preset:\s*(\S+)/);
216
+ const defaultModelMatch = text.match(/defaults:\s*\{\s*model:\s*(\S+?)\s*\}/);
204
217
  const agentTeamsMatch = text.match(/^agent_teams:\s*(\S+)/m);
205
218
  // Parse selected agents list
206
219
  const selectedMatch = text.match(/selected:\s*\[([^\]]*)\]/);
@@ -219,18 +232,26 @@ function registerSettingsRoutes(deps) {
219
232
  const overrideLines = block.match(/^ {2,}(\S+):\s*(\S+)/gm) ?? [];
220
233
  for (const line of overrideLines) {
221
234
  const m = line.match(/^\s+(\S+):\s*(\S+)/);
222
- if (m)
235
+ if (m && (0, spec_models_1.isValidModelForProvider)(m[2], provider)) {
223
236
  existingOverrides[m[1]] = m[2];
237
+ }
224
238
  }
225
239
  }
240
+ const parsedDefaultModel = defaultModelMatch?.[1];
226
241
  existingConfig = {
227
242
  version: versionMatch ? parseInt(versionMatch[1], 10) : 1,
228
- provider: providerMatch ? providerMatch[1] : 'claude',
243
+ // The project provider is authoritative. A stale shared config can
244
+ // belong to another provider after a multi-provider setup pass.
245
+ provider,
229
246
  tier: tierMatch ? tierMatch[1] : 'quick',
230
247
  agents: { selected: parsedSelected, excluded: parsedExcluded },
231
248
  models: {
232
249
  preset: presetMatch ? presetMatch[1] : 'balanced',
233
- defaults: { model: 'sonnet' },
250
+ defaults: {
251
+ model: parsedDefaultModel && (0, spec_models_1.isValidModelForProvider)(parsedDefaultModel, provider)
252
+ ? parsedDefaultModel
253
+ : providerDefault,
254
+ },
234
255
  overrides: existingOverrides,
235
256
  },
236
257
  agent_teams: agentTeamsMatch ? agentTeamsMatch[1] === 'true' : false,
@@ -12,7 +12,6 @@ const ids_1 = require("./ids");
12
12
  const db_1 = require("./db");
13
13
  const desktop_db_1 = require("./desktop-db");
14
14
  const queue_manager_1 = require("./queue-manager");
15
- const command_resolver_1 = require("./command-resolver");
16
15
  const changes_reader_1 = require("./changes-reader");
17
16
  const project_router_helpers_1 = require("./project-router-helpers");
18
17
  const install_config_path_1 = require("./install-config-path");
@@ -172,10 +171,10 @@ function registerSetupRoutes(deps) {
172
171
  res.status(400).json({ error: 'idea is required' });
173
172
  return;
174
173
  }
175
- // Pre-check: does the propose-feature command exist in this project?
176
- const testCmd = `/specrails:propose-feature test`;
177
- const resolved = (0, command_resolver_1.resolveCommand)(testCmd, ctx(req).project.path);
178
- if (resolved === testCmd) {
174
+ // Provider-aware pre-check. The manager resolves against the lazy
175
+ // execution cwd (including relocated workspaces) and understands Kimi's
176
+ // materialized headless skills rather than probing only Claude markdown.
177
+ if (!ctx(req).proposalManager.canStartExploration()) {
179
178
  res.status(400).json({ error: 'This project does not have the /specrails:propose-feature command installed. Run "npx specrails-core@latest" to update.' });
180
179
  return;
181
180
  }
@@ -361,12 +361,24 @@ function registerSpendingRoutes(deps) {
361
361
  // and skewed the day boundary by the tz offset, so the meter disagreed
362
362
  // with both the /analytics dashboard and Claude's per-day accounting.
363
363
  const { startIso, endIso } = localDayBoundsUtc(parseTzOffsetMinutes(req.query.tzOffsetMinutes));
364
- const costRow = db.prepare(`SELECT COALESCE(SUM(total_cost_usd), 0) as costToday FROM ai_invocations WHERE started_at >= ? AND started_at < ?`).get(startIso, endIso);
364
+ const costRow = db.prepare(`SELECT
365
+ COALESCE(SUM(total_cost_usd), 0) as costToday,
366
+ COUNT(total_cost_usd) as pricedRuns,
367
+ SUM(CASE WHEN total_cost_usd IS NULL THEN 1 ELSE 0 END) as unpricedRuns
368
+ FROM ai_invocations WHERE started_at >= ? AND started_at < ?`).get(startIso, endIso);
365
369
  const costToday = costRow.costToday;
366
- const budgetUtilizationPct = dailyBudgetUsd != null && dailyBudgetUsd > 0
370
+ const unpricedRuns = costRow.unpricedRuns ?? 0;
371
+ const budgetUtilizationPct = dailyBudgetUsd != null && dailyBudgetUsd > 0 && unpricedRuns === 0
367
372
  ? (costToday / dailyBudgetUsd) * 100
368
373
  : null;
369
- res.json({ dailyBudgetUsd, jobCostThresholdUsd, costToday, budgetUtilizationPct });
374
+ res.json({
375
+ dailyBudgetUsd,
376
+ jobCostThresholdUsd,
377
+ costToday,
378
+ pricedRuns: costRow.pricedRuns,
379
+ unpricedRuns,
380
+ budgetUtilizationPct,
381
+ });
370
382
  }
371
383
  catch (err) {
372
384
  console.error('[project-router] budget get error:', err);