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
@@ -50,6 +50,7 @@ const explore_smash_1 = require("./explore-smash");
50
50
  const ai_invocations_1 = require("./ai-invocations");
51
51
  const context_scope_1 = require("./context-scope");
52
52
  const result_event_1 = require("./result-event");
53
+ const runtime_1 = require("./providers/runtime");
53
54
  const crypto_1 = require("crypto");
54
55
  const spec_models_1 = require("./spec-models");
55
56
  const provider_selection_1 = require("./provider-selection");
@@ -64,6 +65,48 @@ const workspace_resolution_1 = require("./workspace-resolution");
64
65
  const multer_1 = __importDefault(require("multer"));
65
66
  const attachment_manager_1 = require("./attachment-manager");
66
67
  const project_router_helpers_1 = require("./project-router-helpers");
68
+ /**
69
+ * A NULL conversation provider is the intentional single-provider encoding:
70
+ * it means "use this project's primary provider", never "use Claude".
71
+ */
72
+ function resolveConversationProvider(context, conversationId) {
73
+ const conversation = (0, db_1.getConversation)(context.db, conversationId);
74
+ if (!conversation)
75
+ return null;
76
+ return conversation.provider ?? context.project.provider ?? 'claude';
77
+ }
78
+ /**
79
+ * Resolve a ticket-bound structured action without silently changing engines.
80
+ * An origin conversation owns the action provider because its session/model
81
+ * state is provider-specific. Explicit requests may confirm that provider but
82
+ * cannot replace it; missing/stale origins fail closed.
83
+ */
84
+ function resolveTicketBoundProvider(context, ticket, requested) {
85
+ const requestedCheck = (0, provider_selection_1.validateRequestedProvider)(context.project, requested);
86
+ if (!requestedCheck.ok) {
87
+ return { ok: false, status: 400, error: requestedCheck.error };
88
+ }
89
+ if (!ticket.origin_conversation_id) {
90
+ return { ok: true, provider: requestedCheck.provider };
91
+ }
92
+ const originProvider = resolveConversationProvider(context, ticket.origin_conversation_id);
93
+ if (!originProvider) {
94
+ return { ok: false, status: 409, error: 'ticket_origin_conversation_not_found' };
95
+ }
96
+ const originCheck = (0, provider_selection_1.validateRequestedProvider)(context.project, originProvider);
97
+ if (!originCheck.ok) {
98
+ return { ok: false, status: 409, error: originCheck.error };
99
+ }
100
+ const hasExplicitRequest = requested !== undefined && requested !== null && requested !== '';
101
+ if (hasExplicitRequest && requestedCheck.provider !== originCheck.provider) {
102
+ return {
103
+ ok: false,
104
+ status: 400,
105
+ error: `provider '${requestedCheck.provider}' does not match ticket origin provider '${originCheck.provider}'`,
106
+ };
107
+ }
108
+ return { ok: true, provider: originCheck.provider };
109
+ }
67
110
  /**
68
111
  * Add Spec on a Jira-backed project: promote the freshly-created LOCAL ticket to
69
112
  * a Jira issue (best-effort, server-side). The per-spec `createLocal` escape
@@ -182,10 +225,12 @@ function registerTicketsRoutes(deps) {
182
225
  let hasAttachments = false;
183
226
  let baseUserPrompt = `Generate a spec for the following idea:\n\n${idea.trim()}`;
184
227
  let imageFlags = [];
228
+ let imagePaths = [];
185
229
  if (attachmentIds.length > 0 && pendingSpecId) {
186
230
  try {
187
231
  const extracted = await attachment_manager_1.attachmentManager.getClaudeArgs(project.slug, pendingSpecId, attachmentIds);
188
232
  imageFlags = extracted.imageFlags;
233
+ imagePaths = extracted.imagePaths;
189
234
  if (extracted.textBlocks.length > 0) {
190
235
  hasAttachments = true;
191
236
  baseUserPrompt = `${baseUserPrompt}\n\n## Attached Resources\n\n${extracted.textBlocks.join('\n\n')}`;
@@ -203,9 +248,9 @@ function registerTicketsRoutes(deps) {
203
248
  // Awareness controls; Quick still keeps Contract Refine as a top-level
204
249
  // field for the refine scheduler.
205
250
  const rawScope = req.body?.contextScope;
206
- // Contract Layer is Claude-only — force it off for any non-claude engine
207
- // (defence-in-depth; the Quick UI hides the toggle for those).
208
- const quickContractRefine = provider !== 'claude'
251
+ // Contract Layer is available only when the selected adapter advertises a
252
+ // safe structured-action implementation.
253
+ const quickContractRefine = (0, providers_1.getAdapter)(provider).capabilities.structuredActions !== true
209
254
  ? false
210
255
  : typeof req.body?.contractRefine === 'boolean'
211
256
  ? req.body.contractRefine
@@ -224,6 +269,18 @@ function registerTicketsRoutes(deps) {
224
269
  contractRefine: quickContractRefine,
225
270
  userMcp: typeof rawScope?.userMcp === 'boolean' ? rawScope.userMcp : false,
226
271
  };
272
+ const adapter = (0, providers_1.getAdapter)(provider);
273
+ const quickToolPolicy = quickScope.full
274
+ ? ((0, runtime_1.supportsToolPolicy)(adapter, 'read-only') ? 'read-only' : null)
275
+ : (0, runtime_1.pureOutputToolPolicy)(adapter);
276
+ if (!quickToolPolicy) {
277
+ res.status(409).json({
278
+ error: 'provider_tool_policy_unsupported',
279
+ provider,
280
+ requiredPolicy: quickScope.full ? 'read-only' : 'pure-output',
281
+ });
282
+ return;
283
+ }
227
284
  // Persist Quick mode Contract Refine choice (per-project last value).
228
285
  (0, db_1.setQuickContractRefineLast)(ctx(req).db, quickContractRefine);
229
286
  // Relocate-artifacts: tickets read from the workspace when relocated.
@@ -278,24 +335,26 @@ function registerTicketsRoutes(deps) {
278
335
  // same way. For codex the system prompt folds into the user prompt
279
336
  // (no --system-prompt flag) and the extra Claude-only flags are ignored
280
337
  // by the codex adapter (it doesn't read extraArgs that don't apply).
281
- const adapter = (0, providers_1.getAdapter)(provider);
282
338
  const toolFlags = provider === 'claude' ? (0, context_scope_1.toolFlagsForScope)(quickScope) : { args: [] };
283
339
  // Full scope grants Read/Grep/Glob. The model spends turns exploring the
284
340
  // repo before it writes the spec; 6 was too tight (a few tool calls on a
285
341
  // sparse/empty repo hit error_max_turns → exit 1 → opaque failure). 15
286
342
  // leaves comfortable headroom while --max-turns still bounds runaway loops.
287
343
  const claudeMaxTurns = quickScope.full ? 15 : (hasAttachments ? 3 : 1);
288
- const args = adapter.buildArgs('spec-gen', {
344
+ const specGenOptions = {
289
345
  prompt: userPrompt,
290
346
  systemPrompt,
291
347
  model: resolvedModel,
348
+ toolPolicy: quickToolPolicy,
292
349
  maxTurns: provider === 'claude' ? claudeMaxTurns : undefined,
293
350
  extraArgs: provider === 'claude' ? [...toolFlags.args, ...imageFlags] : undefined,
294
351
  // "My approved MCPs" (scope.userMcp) loads the developer's user-scope,
295
352
  // plugin, and connector MCP servers (claude-only). Quick already spawns
296
353
  // from project.path so project `.mcp.json` is discovered without a flag.
297
- loadUserEnv: provider === 'claude' && quickScope.userMcp,
298
- });
354
+ loadUserEnv: adapter.capabilities.userMcp === true && quickScope.userMcp,
355
+ imagePaths: adapter.capabilities.supportsImageInput ? imagePaths : undefined,
356
+ };
357
+ const args = adapter.buildArgs('spec-gen', specGenOptions);
299
358
  const binary = adapter.binary;
300
359
  // Relocate-artifacts gate: spawn from the workspace + SPECRAILS_REPO_DIR when
301
360
  // relocated, else cwd = project.path + process.env (byte-identical legacy).
@@ -310,7 +369,7 @@ function registerTicketsRoutes(deps) {
310
369
  // POSIX argv path unchanged.
311
370
  console.log(`[project-router] spec-gen spawn: ${binary} (cwd=${specGenExec.cwd}, requestId=${requestId})`);
312
371
  const child = (0, cli_prompt_1.spawnAiCli)(binary, args, {
313
- env: specGenEnv,
372
+ env: (0, runtime_1.buildProviderEnv)(adapter, specGenOptions, specGenEnv),
314
373
  stdio: ['ignore', 'pipe', 'pipe'],
315
374
  cwd: specGenExec.cwd,
316
375
  });
@@ -386,80 +445,23 @@ function registerTicketsRoutes(deps) {
386
445
  stdoutReader.on('line', (line) => {
387
446
  if (!admission.isCurrent())
388
447
  return;
389
- const adapterEv = adapter.parseStreamLine(line);
390
- if (adapterEv)
391
- adapterEvents.push(adapterEv);
448
+ const eventsForLine = (0, runtime_1.parseStreamEvents)(adapter, line);
449
+ adapterEvents.push(...eventsForLine);
392
450
  let parsed = null;
393
451
  try {
394
452
  parsed = JSON.parse(line);
395
453
  }
396
454
  catch { /* skip */ }
397
- if (!parsed)
398
- return;
399
- if (provider === 'codex') {
400
- // Codex `exec --json` emits one event per line. Capture the final
401
- // `turn.completed` for usage extraction, and accumulate ONLY the
402
- // assistant_message text — never the command_execution items or
403
- // wrapper events, otherwise the raw JSONL ends up in the ticket
404
- // description.
405
- if (parsed.type === 'turn.completed') {
406
- lastResultEvent = parsed;
407
- return;
408
- }
409
- if (parsed.type !== 'item.completed')
410
- return;
411
- const item = parsed.item;
412
- if (!item || item.type !== 'agent_message')
413
- return;
414
- const newText = (item.text ?? '').trim();
415
- if (!newText)
416
- return;
417
- // Each agent_message is a complete chunk — separate with a blank
418
- // line so the parser regexes match cleanly across chunks.
419
- buffer += (buffer.endsWith('\n') || buffer.length === 0 ? '' : '\n') + newText + '\n';
420
- const msg = {
421
- type: 'spec_gen_stream', projectId, requestId,
422
- delta: newText + '\n', timestamp: new Date().toISOString(),
423
- };
424
- broadcast(msg);
425
- return;
426
- }
427
- if (provider === 'claude') {
428
- // Claude path.
429
- if (parsed.type === 'result') {
430
- lastResultEvent = parsed;
431
- }
432
- if (parsed.type === 'assistant') {
433
- const msg = parsed.message;
434
- const texts = (msg?.content ?? [])
435
- .filter((c) => c.type === 'text')
436
- .map((c) => c.text ?? '');
437
- const newText = texts.join('');
438
- if (newText) {
439
- buffer += newText;
440
- const wsMsg = {
441
- type: 'spec_gen_stream', projectId, requestId,
442
- delta: newText, timestamp: new Date().toISOString(),
443
- };
444
- broadcast(wsMsg);
445
- }
446
- }
447
- return;
448
- }
449
- // Adapter-driven path (gemini + future providers): the adapter already
450
- // parsed this line into a structured AdapterEvent above (`adapterEv`).
451
- // Accumulate assistant text deltas into the buffer and capture the result
452
- // event for usage extraction — mirrors the ai-edit endpoint's else branch.
453
- // Without this, gemini (which emits `type:'message'`, not `type:'assistant'`)
454
- // would never accumulate text → empty buffer → "Empty response from AI".
455
- if (adapterEv?.kind === 'result') {
455
+ if (parsed && eventsForLine.some((event) => event.kind === 'result')) {
456
456
  lastResultEvent = parsed;
457
457
  }
458
- if (adapterEv?.kind === 'text-delta' && adapterEv.text) {
459
- buffer += adapterEv.text;
458
+ for (const event of eventsForLine) {
459
+ if (event.kind !== 'text-delta' || !event.text)
460
+ continue;
461
+ buffer += event.text;
460
462
  const wsMsg = {
461
463
  type: 'spec_gen_stream', projectId, requestId,
462
- delta: adapterEv.text, timestamp: new Date().toISOString(),
464
+ delta: event.text, timestamp: new Date().toISOString(),
463
465
  };
464
466
  broadcast(wsMsg);
465
467
  }
@@ -569,14 +571,8 @@ function registerTicketsRoutes(deps) {
569
571
  // Add Spec → Jira: promote the new ticket to a Jira issue when the
570
572
  // project is Jira-backed (unless the user opted to keep it local).
571
573
  void maybePromoteSpecToJira(ctx(req), created.id, req.body?.createLocal === true, broadcast);
572
- // Quick mode Contract Refine: when toggle is on in the request body
573
- // AND the project setting + kill switch permit it, fire the no-resume
574
- // Quick refine path asynchronously. Claude-only today — codex
575
- // contract refine isn't wired (the spawn hardcodes the `claude`
576
- // binary). Skip silently on codex projects so the ticket lands
577
- // without the misleading "Contract layer skipped — model_error"
578
- // toast that the refine kill-switch would otherwise emit.
579
- if (quickContractRefine && created && provider === 'claude') {
574
+ // Quick mode Contract Refine runs on the same selected provider.
575
+ if (quickContractRefine && created) {
580
576
  const refineTicketId = created.id;
581
577
  const refineTitle = created.title;
582
578
  const refineDescription = created.description;
@@ -590,16 +586,13 @@ function registerTicketsRoutes(deps) {
590
586
  projectSlug: project.slug,
591
587
  projectPath: project.path,
592
588
  projectName: project.name,
589
+ providerId: provider,
593
590
  broadcast: broadcast,
594
591
  }, refineTicketId, refineTitle, refineDescription, refineModel).catch((err) => {
595
592
  console.error('[project-router] runContractRefineForQuick error:', err);
596
593
  });
597
594
  });
598
595
  }
599
- else if (quickContractRefine && created && provider === 'codex') {
600
- console.log(`[project-router] quick contract refine skipped for codex project (ticket #${created.id}); ` +
601
- `feature is claude-only today`);
602
- }
603
596
  }
604
597
  catch (err) {
605
598
  console.error('[project-router] generate-spec ticket creation error:', err);
@@ -1022,17 +1015,18 @@ function registerTicketsRoutes(deps) {
1022
1015
  // Add Spec (Explore) → Jira: promote the committed ticket to a Jira issue
1023
1016
  // when the project is Jira-backed (unless the user opted to keep it local).
1024
1017
  void maybePromoteSpecToJira(ctx(req), created.id, body.createLocal === true, broadcast);
1025
- // Fire Contract Refine post-commit (fire-and-forget). Toggle + kill-switch
1026
- // are checked inside runContractRefine. Claude-only today — codex
1027
- // contract refine isn't wired (the spawn hardcodes the `claude`
1028
- // binary). Skip silently on codex projects.
1018
+ // Fire Contract Refine post-commit (fire-and-forget). Toggle, kill-switch,
1019
+ // and provider capability are checked inside runContractRefine.
1029
1020
  // Gate on the CONVERSATION's own engine, not the project's PRIMARY
1030
1021
  // provider: a multi-provider project with a non-claude primary can still
1031
1022
  // run an Explore on the claude engine + opt into Contract Refine.
1032
1023
  const convoProvider = conversationId
1033
- ? ((0, db_1.getConversation)(ctx(req).db, conversationId)?.provider ?? 'claude')
1024
+ ? resolveConversationProvider(ctx(req), conversationId)
1034
1025
  : null;
1035
- if (conversationId && created && convoProvider === 'claude') {
1026
+ if (conversationId &&
1027
+ created &&
1028
+ convoProvider &&
1029
+ (0, providers_1.getAdapter)(convoProvider).capabilities.structuredActions === true) {
1036
1030
  const createdTicketId = created.id;
1037
1031
  const convoId = conversationId;
1038
1032
  console.log(`[project-router] from-draft hook: scheduling refine ticket=${createdTicketId} conv=${convoId}`);
@@ -1043,13 +1037,16 @@ function registerTicketsRoutes(deps) {
1043
1037
  projectSlug: project.slug,
1044
1038
  projectPath: project.path,
1045
1039
  projectName: project.name,
1040
+ // A NULL conversation.provider means project primary. Thread the
1041
+ // already-resolved value so the runner never falls back to Claude.
1042
+ providerId: convoProvider,
1046
1043
  broadcast: broadcast,
1047
1044
  }, convoId, createdTicketId).catch((err) => {
1048
1045
  console.error('[project-router] runContractRefine error:', err);
1049
1046
  });
1050
1047
  });
1051
1048
  }
1052
- else if (conversationId && created && convoProvider && convoProvider !== 'claude') {
1049
+ else if (conversationId && created && convoProvider) {
1053
1050
  console.log(`[project-router] from-draft contract refine skipped for ${convoProvider} conversation (ticket #${created.id})`);
1054
1051
  }
1055
1052
  else if (!conversationId && draftTicketId === null && created && body.contractRefine === true) {
@@ -1065,7 +1062,8 @@ function registerTicketsRoutes(deps) {
1065
1062
  const projectProviders = Array.isArray(project.providers) && project.providers.length > 0
1066
1063
  ? project.providers
1067
1064
  : [project.provider].filter((p) => typeof p === 'string');
1068
- if (projectProviders.includes('claude')) {
1065
+ const refineProvider = projectProviders.find((id) => (0, providers_1.getAdapter)(id).capabilities.structuredActions === true);
1066
+ if (refineProvider) {
1069
1067
  const refineTicketId = created.id;
1070
1068
  const refineTitle = created.title;
1071
1069
  const refineDescription = created.description;
@@ -1077,6 +1075,7 @@ function registerTicketsRoutes(deps) {
1077
1075
  projectSlug: project.slug,
1078
1076
  projectPath: project.path,
1079
1077
  projectName: project.name,
1078
+ providerId: refineProvider,
1080
1079
  broadcast: broadcast,
1081
1080
  }, refineTicketId, refineTitle, refineDescription, null).catch((err) => {
1082
1081
  console.error('[project-router] from-draft runContractRefineForQuick error:', err);
@@ -1084,7 +1083,7 @@ function registerTicketsRoutes(deps) {
1084
1083
  });
1085
1084
  }
1086
1085
  else {
1087
- console.log(`[project-router] from-draft contract refine skipped (ticket #${created.id}): claude not installed on project`);
1086
+ console.log(`[project-router] from-draft contract refine skipped (ticket #${created.id}): no capable provider installed`);
1088
1087
  }
1089
1088
  }
1090
1089
  }
@@ -1205,10 +1204,6 @@ function registerTicketsRoutes(deps) {
1205
1204
  res.status(409).json({ error: 'feature_disabled_by_env' });
1206
1205
  return;
1207
1206
  }
1208
- if (project.provider === 'codex') {
1209
- res.status(409).json({ error: 'contract_refine_unsupported_for_codex' });
1210
- return;
1211
- }
1212
1207
  // Validate the ticket exists.
1213
1208
  try {
1214
1209
  const filePath = ticketPath(req);
@@ -1218,6 +1213,16 @@ function registerTicketsRoutes(deps) {
1218
1213
  res.status(404).json({ error: 'ticket not found' });
1219
1214
  return;
1220
1215
  }
1216
+ const refineProviderCheck = resolveTicketBoundProvider({ project, db }, ticket, req.body?.provider ?? req.body?.aiEngine);
1217
+ if (!refineProviderCheck.ok) {
1218
+ res.status(refineProviderCheck.status).json({ error: refineProviderCheck.error });
1219
+ return;
1220
+ }
1221
+ const refineProvider = refineProviderCheck.provider;
1222
+ if ((0, providers_1.getAdapter)(refineProvider).capabilities.structuredActions !== true) {
1223
+ res.status(409).json({ error: `contract_refine_unsupported_for_${refineProvider}` });
1224
+ return;
1225
+ }
1221
1226
  if (!ticket.origin_conversation_id) {
1222
1227
  // No Explore origin (agent-authored commit_draft / Quick spec that
1223
1228
  // skipped the refine): fall back to the Quick-style refine, seeded
@@ -1234,6 +1239,7 @@ function registerTicketsRoutes(deps) {
1234
1239
  projectSlug: project.slug,
1235
1240
  projectPath: project.path,
1236
1241
  projectName: project.name,
1242
+ providerId: refineProvider,
1237
1243
  broadcast: broadcast,
1238
1244
  }, ticketId, seedTitle, seedBody, null).catch((err) => {
1239
1245
  console.error('[project-router] retry runContractRefineForQuick error:', err);
@@ -1250,6 +1256,7 @@ function registerTicketsRoutes(deps) {
1250
1256
  projectSlug: project.slug,
1251
1257
  projectPath: project.path,
1252
1258
  projectName: project.name,
1259
+ providerId: refineProvider,
1253
1260
  broadcast: broadcast,
1254
1261
  ignoreConversationScope: true,
1255
1262
  }, convoId, ticketId).catch((err) => {
@@ -1284,9 +1291,27 @@ function registerTicketsRoutes(deps) {
1284
1291
  res.status(statusCode).json({ error: 'ineligible', reason: gate.reason });
1285
1292
  return;
1286
1293
  }
1294
+ const ticket = store.tickets[String(ticketId)];
1287
1295
  const rawMode = typeof req.body?.mode === 'string' ? req.body.mode : 'simple';
1288
1296
  const mode = rawMode === 'full' ? 'full' : 'simple';
1297
+ const providerCheck = resolveTicketBoundProvider({ project, db }, ticket, req.body?.provider ?? req.body?.aiEngine);
1298
+ if (!providerCheck.ok) {
1299
+ res.status(providerCheck.status).json({ error: providerCheck.error });
1300
+ return;
1301
+ }
1302
+ const smashProvider = providerCheck.provider;
1303
+ if ((0, providers_1.getAdapter)(smashProvider).capabilities.structuredActions !== true) {
1304
+ res.status(409).json({ error: `smash_unsupported_for_${smashProvider}` });
1305
+ return;
1306
+ }
1289
1307
  const model = typeof req.body?.model === 'string' && req.body.model.length > 0 ? req.body.model : null;
1308
+ if (model && !(0, spec_models_1.isValidModelForProvider)(model, smashProvider)) {
1309
+ res.status(400).json({
1310
+ error: `model is not valid for provider "${smashProvider}"`,
1311
+ allowed: (0, spec_models_1.getModelsForProvider)(smashProvider),
1312
+ });
1313
+ return;
1314
+ }
1290
1315
  res.status(202).json({ scheduled: true, mode });
1291
1316
  process.nextTick(() => {
1292
1317
  void (0, smash_runner_1.runSmash)({
@@ -1295,6 +1320,7 @@ function registerTicketsRoutes(deps) {
1295
1320
  projectSlug: project.slug,
1296
1321
  projectPath: project.path,
1297
1322
  projectName: project.name,
1323
+ providerId: smashProvider,
1298
1324
  // Relocate-artifacts: pass the gated tickets-store path so SMASH
1299
1325
  // reads/writes the same store the rails load (workspace when
1300
1326
  // relocated), not a stale repo copy.
@@ -1364,7 +1390,27 @@ function registerTicketsRoutes(deps) {
1364
1390
  }
1365
1391
  const { project, broadcast, ticketWatcher } = ctx(req);
1366
1392
  try {
1393
+ // This destructive helper exists solely as the first half of Re-SMASH.
1394
+ // Enforce the same provider capability before touching the ticket store;
1395
+ // otherwise a Kimi Re-SMASH could delete every existing child and only
1396
+ // then have POST /smash reject the unsupported structured action.
1367
1397
  const filePath = ticketPath(req);
1398
+ const store = (0, ticket_store_1.readStore)(filePath);
1399
+ const ticket = store.tickets[String(ticketId)];
1400
+ if (!ticket) {
1401
+ res.status(404).json({ error: 'ticket not found' });
1402
+ return;
1403
+ }
1404
+ const providerCheck = resolveTicketBoundProvider(ctx(req), ticket, req.query.provider ?? req.body?.provider ?? req.body?.aiEngine);
1405
+ if (!providerCheck.ok) {
1406
+ res.status(providerCheck.status).json({ error: providerCheck.error });
1407
+ return;
1408
+ }
1409
+ const smashProvider = providerCheck.provider;
1410
+ if ((0, providers_1.getAdapter)(smashProvider).capabilities.structuredActions !== true) {
1411
+ res.status(409).json({ error: `smash_unsupported_for_${smashProvider}` });
1412
+ return;
1413
+ }
1368
1414
  const result = (0, smash_runner_1.applyDeleteEpicChildren)(filePath, ticketId);
1369
1415
  // Pass the real post-write revision (not 0) so the chokidar echo is
1370
1416
  // suppressed; a hardcoded 0 never matches the on-disk revision and
@@ -1590,15 +1636,33 @@ function registerTicketsRoutes(deps) {
1590
1636
  const priorProposal = typeof priorProposalRaw === 'string' && priorProposalRaw.length > 0 ? priorProposalRaw : null;
1591
1637
  const isRefinement = priorProposal !== null;
1592
1638
  const { project, broadcast } = ctx(req);
1593
- const provider = project.provider ?? 'claude';
1639
+ const providerCheck = (0, provider_selection_1.validateRequestedProvider)(project, req.body?.provider ?? req.body?.aiEngine);
1640
+ if (!providerCheck.ok) {
1641
+ res.status(400).json({ error: providerCheck.error });
1642
+ return;
1643
+ }
1644
+ const provider = providerCheck.provider;
1594
1645
  const adapter = (0, providers_1.getAdapter)(provider);
1646
+ if (!(0, runtime_1.supportsToolPolicy)(adapter, 'read-only')) {
1647
+ res.status(409).json({
1648
+ error: 'provider_tool_policy_unsupported',
1649
+ provider,
1650
+ requiredPolicy: 'read-only',
1651
+ });
1652
+ return;
1653
+ }
1595
1654
  const requestId = (0, ids_1.newId)();
1596
1655
  const projectId = project.id;
1597
1656
  const admission = (0, process_admission_1.captureProcessAdmission)(projectId);
1598
- // Model used for the spawn (claude runs its CLI default, codex is pinned to
1599
- // gpt-5.5, other providers use the adapter default). Used only as the
1600
- // fallback model for cost accounting when the stream carries none.
1601
- const aiEditModel = provider === 'codex' ? 'gpt-5.5' : adapter.defaultModel();
1657
+ const requestedAiEditModel = typeof req.body?.model === 'string' ? req.body.model : adapter.defaultModel();
1658
+ if (!(0, spec_models_1.isValidModelForProvider)(requestedAiEditModel, provider)) {
1659
+ res.status(400).json({
1660
+ error: `model is not valid for provider "${provider}"`,
1661
+ allowed: (0, spec_models_1.getModelsForProvider)(provider),
1662
+ });
1663
+ return;
1664
+ }
1665
+ const aiEditModel = requestedAiEditModel;
1602
1666
  // Build the focused pre-prompt
1603
1667
  const baseRules = `- Output format MUST be exactly:\n` +
1604
1668
  ` TITLE: <one-line spec title>\n` +
@@ -1635,10 +1699,12 @@ function registerTicketsRoutes(deps) {
1635
1699
  `## User Instructions\n\n${instructions.trim()}\n\n` +
1636
1700
  `Output the modified TITLE line followed by the modified description now.`;
1637
1701
  let imageFlags = [];
1702
+ let aiEditImagePaths = [];
1638
1703
  if (attachmentIds.length > 0) {
1639
1704
  try {
1640
1705
  const extracted = await attachment_manager_1.attachmentManager.getClaudeArgs(project.slug, ticketId, attachmentIds);
1641
1706
  imageFlags = extracted.imageFlags;
1707
+ aiEditImagePaths = extracted.imagePaths;
1642
1708
  if (extracted.textBlocks.length > 0) {
1643
1709
  systemPrompt = `${systemPrompt}\n\n${attachment_manager_1.USER_ATTACHMENT_SYSTEM_NOTE}`;
1644
1710
  userPrompt = `${userPrompt}\n\n## Attached Files\n\n${extracted.textBlocks.join('\n\n')}`;
@@ -1652,43 +1718,21 @@ function registerTicketsRoutes(deps) {
1652
1718
  res.status(409).json({ error: 'Project is being removed' });
1653
1719
  return;
1654
1720
  }
1655
- let binary;
1656
- let args;
1657
- if (provider === 'codex') {
1658
- binary = 'codex';
1659
- // Use gpt-5.5 (default for Codex per CODEX_MODELS/PRESET_DEFAULTS in ModelSelector); never hardcode o4-mini
1660
- args = ['exec', `${systemPrompt}\n\n${userPrompt}`, '--model', 'gpt-5.5'];
1661
- }
1662
- else if (provider === 'claude') {
1663
- binary = 'claude';
1664
- args = [
1665
- '--dangerously-skip-permissions',
1666
- // AI Edit may ground prose in the repo, but it never needs shell,
1667
- // network, MCP, or filesystem mutation authority.
1668
- '--tools', 'Read,Grep,Glob',
1669
- '--output-format', 'stream-json',
1670
- '--verbose',
1671
- '--max-turns', '4',
1672
- ...imageFlags,
1673
- '--system-prompt', systemPrompt,
1674
- '-p', userPrompt,
1675
- ];
1676
- }
1677
- else {
1678
- // Adapter-driven path (gemini + any future provider): binary, argv, and
1679
- // stream parsing all come from the registered adapter — no per-provider
1680
- // hardcoding. The claude/codex shapes above are kept byte-identical.
1681
- binary = adapter.binary;
1682
- args = adapter.buildArgs('agent-refine', {
1683
- prompt: userPrompt,
1684
- systemPrompt,
1685
- model: adapter.defaultModel(),
1686
- });
1687
- }
1721
+ const binary = adapter.binary;
1722
+ const aiEditOptions = {
1723
+ prompt: userPrompt,
1724
+ systemPrompt,
1725
+ model: aiEditModel,
1726
+ toolPolicy: 'read-only',
1727
+ maxTurns: 4,
1728
+ extraArgs: provider === 'claude' ? imageFlags : undefined,
1729
+ imagePaths: adapter.capabilities.supportsImageInput ? aiEditImagePaths : undefined,
1730
+ };
1731
+ const args = adapter.buildArgs('agent-refine', aiEditOptions);
1688
1732
  // spawnAiCli reroutes multi-line argv values through stdin on Windows.
1689
1733
  console.log(`[project-router] ai-edit spawn: ${binary} (cwd=${project.path}, requestId=${requestId})`);
1690
1734
  const child = (0, cli_prompt_1.spawnAiCli)(binary, args, {
1691
- env: process.env,
1735
+ env: (0, runtime_1.buildProviderEnv)(adapter, aiEditOptions),
1692
1736
  stdio: ['ignore', 'pipe', 'pipe'],
1693
1737
  cwd: project.path,
1694
1738
  });
@@ -1730,56 +1774,18 @@ function registerTicketsRoutes(deps) {
1730
1774
  const aiEditStartedAt = new Date().toISOString();
1731
1775
  const stdoutReader = (0, readline_1.createInterface)({ input: child.stdout, crlfDelay: Infinity });
1732
1776
  stdoutReader.on('line', (line) => {
1733
- const aev = adapter.parseStreamLine(line);
1734
- if (aev)
1735
- adapterEvents.push(aev);
1736
- if (provider === 'codex') {
1737
- if (line) {
1738
- buffer += line + '\n';
1777
+ const eventsForLine = (0, runtime_1.parseStreamEvents)(adapter, line);
1778
+ adapterEvents.push(...eventsForLine);
1779
+ for (const event of eventsForLine) {
1780
+ if (event.kind === 'text-delta' && event.text) {
1781
+ buffer += event.text;
1739
1782
  const msg = {
1740
1783
  type: 'ticket_ai_edit_stream', projectId, ticketId: Number(ticketId),
1741
- requestId, delta: line + '\n', timestamp: new Date().toISOString(),
1784
+ requestId, delta: event.text, timestamp: new Date().toISOString(),
1742
1785
  };
1743
1786
  broadcast(msg);
1744
1787
  }
1745
1788
  }
1746
- else if (provider === 'claude') {
1747
- let parsed = null;
1748
- try {
1749
- parsed = JSON.parse(line);
1750
- }
1751
- catch { /* skip */ }
1752
- if (!parsed)
1753
- return;
1754
- if (parsed.type === 'assistant') {
1755
- const msg = parsed.message;
1756
- const texts = (msg?.content ?? [])
1757
- .filter((c) => c.type === 'text')
1758
- .map((c) => c.text ?? '');
1759
- const newText = texts.join('');
1760
- if (newText) {
1761
- buffer += newText;
1762
- const wsMsg = {
1763
- type: 'ticket_ai_edit_stream', projectId, ticketId: Number(ticketId),
1764
- requestId, delta: newText, timestamp: new Date().toISOString(),
1765
- };
1766
- broadcast(wsMsg);
1767
- }
1768
- }
1769
- }
1770
- else {
1771
- // Adapter-driven parse (gemini + future providers): uniform AdapterEvent
1772
- // stream — accumulate assistant text deltas (reuse the event parsed above).
1773
- const ev = aev;
1774
- if (ev?.kind === 'text-delta' && ev.text) {
1775
- buffer += ev.text;
1776
- const wsMsg = {
1777
- type: 'ticket_ai_edit_stream', projectId, ticketId: Number(ticketId),
1778
- requestId, delta: ev.text, timestamp: new Date().toISOString(),
1779
- };
1780
- broadcast(wsMsg);
1781
- }
1782
- }
1783
1789
  });
1784
1790
  child.on('close', (code) => {
1785
1791
  _aiEditProcesses.delete(requestId);
@@ -134,6 +134,7 @@ function createProjectRouter(registry) {
134
134
  fileSummaryManager: projectCtx.fileSummaryManager,
135
135
  getTicketSpec: projectCtx.getTicketSpec,
136
136
  fileStoryManager,
137
+ aiTransformProvider: storyAdapter.id,
137
138
  // Relocate-artifacts: summary JSON OUTPUTS live in the workspace when
138
139
  // relocated (source tree still read from project.path). Resolved per-call.
139
140
  resolveSummaryRoot: () => {