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
@@ -4,6 +4,7 @@ exports.isProjectBuilderEnabled = isProjectBuilderEnabled;
4
4
  exports.createBlueprintRouter = createBlueprintRouter;
5
5
  const express_1 = require("express");
6
6
  const providers_1 = require("./providers");
7
+ const runtime_1 = require("./providers/runtime");
7
8
  const blueprint_store_1 = require("./blueprint-store");
8
9
  function isProjectBuilderEnabled() {
9
10
  return process.env.SPECRAILS_PROJECT_BUILDER !== 'false';
@@ -32,11 +33,19 @@ function createBlueprintRouter(deps) {
32
33
  router.get('/models', (req, res) => {
33
34
  const provider = validProvider(req.query.provider) ?? 'claude';
34
35
  const adapter = (0, providers_1.getAdapter)(provider);
36
+ const toolPolicy = (0, runtime_1.pureOutputToolPolicy)(adapter);
37
+ const requestedModel = typeof req.query.model === 'string' && req.query.model
38
+ ? req.query.model
39
+ : null;
40
+ const model = requestedModel ?? adapter.defaultModel();
35
41
  res.json({
36
42
  provider,
37
- models: adapter.modelCatalog(),
38
- defaultModel: adapter.defaultModel(),
39
- efforts: adapter.capabilities.reasoningEfforts ?? [],
43
+ available: toolPolicy !== null,
44
+ toolPolicy,
45
+ models: toolPolicy ? adapter.modelCatalog() : [],
46
+ defaultModel: toolPolicy ? adapter.defaultModel() : null,
47
+ model,
48
+ efforts: toolPolicy ? (0, providers_1.reasoningEffortsForModel)(adapter, model) : [],
40
49
  });
41
50
  });
42
51
  router.get('/conversations', (_req, res) => {
@@ -45,6 +54,10 @@ function createBlueprintRouter(deps) {
45
54
  router.post('/conversations', (req, res) => {
46
55
  const body = (req.body ?? {});
47
56
  const provider = validProvider(body.provider) ?? 'claude';
57
+ if (!(0, runtime_1.pureOutputToolPolicy)((0, providers_1.getAdapter)(provider))) {
58
+ res.status(409).json({ error: 'provider_tool_policy_unsupported', provider });
59
+ return;
60
+ }
48
61
  const model = typeof body.model === 'string' && body.model ? body.model : null;
49
62
  const conversation = (0, blueprint_store_1.createBlueprintConversation)(desktopDb, { provider, model });
50
63
  res.status(201).json({ conversation });
@@ -71,6 +84,10 @@ function createBlueprintRouter(deps) {
71
84
  res.status(400).json({ error: 'invalid provider' });
72
85
  return;
73
86
  }
87
+ if (!(0, runtime_1.pureOutputToolPolicy)((0, providers_1.getAdapter)(provider))) {
88
+ res.status(409).json({ error: 'provider_tool_policy_unsupported', provider });
89
+ return;
90
+ }
74
91
  patch.provider = provider;
75
92
  if (provider !== conversation.provider) {
76
93
  // A session id is provider-local; a switch must reset it and the model.
@@ -85,8 +102,7 @@ function createBlueprintRouter(deps) {
85
102
  }
86
103
  const effectiveProvider = patch.provider ?? conversation.provider;
87
104
  if (typeof body.model === 'string' && body.model) {
88
- const valid = new Set((0, providers_1.getAdapter)(effectiveProvider).modelCatalog().map((m) => m.value));
89
- if (!valid.has(body.model)) {
105
+ if (!(0, providers_1.isModelAvailableForAdapter)((0, providers_1.getAdapter)(effectiveProvider), body.model)) {
90
106
  res.status(400).json({ error: 'model not in provider catalog' });
91
107
  return;
92
108
  }
@@ -132,10 +148,28 @@ function createBlueprintRouter(deps) {
132
148
  res.status(409).json({ error: 'a turn is already streaming' });
133
149
  return;
134
150
  }
151
+ const adapter = (0, providers_1.getAdapter)(conversation.provider);
152
+ if (!(0, runtime_1.pureOutputToolPolicy)(adapter)) {
153
+ res.status(409).json({
154
+ error: 'provider_tool_policy_unsupported',
155
+ provider: conversation.provider,
156
+ });
157
+ return;
158
+ }
159
+ if (body.model !== undefined
160
+ && body.model !== null
161
+ && !(0, providers_1.isModelAvailableForAdapter)(adapter, body.model)) {
162
+ res.status(400).json({ error: 'model not in provider catalog' });
163
+ return;
164
+ }
135
165
  const model = typeof body.model === 'string' && body.model ? body.model : undefined;
166
+ const effectiveModel = model
167
+ ?? (conversation.model && (0, providers_1.isModelAvailableForAdapter)(adapter, conversation.model)
168
+ ? conversation.model
169
+ : adapter.defaultModel());
136
170
  let reasoningEffort;
137
171
  if (body.reasoning_effort !== undefined) {
138
- const allowedEfforts = (0, providers_1.getAdapter)(conversation.provider).capabilities.reasoningEfforts ?? [];
172
+ const allowedEfforts = (0, providers_1.reasoningEffortsForModel)(adapter, effectiveModel);
139
173
  if (typeof body.reasoning_effort !== 'string'
140
174
  || !allowedEfforts.includes(body.reasoning_effort)) {
141
175
  res.status(400).json({ error: 'reasoning effort not in provider catalog' });
@@ -17,6 +17,7 @@ const crypto_1 = require("crypto");
17
17
  const spec_draft_parser_1 = require("./spec-draft-parser");
18
18
  const attachment_manager_1 = require("./attachment-manager");
19
19
  const providers_1 = require("./providers");
20
+ const runtime_1 = require("./providers/runtime");
20
21
  const context_scope_1 = require("./context-scope");
21
22
  const user_mcp_config_1 = require("./user-mcp-config");
22
23
  const binary_probe_1 = require("./binary-probe");
@@ -24,6 +25,7 @@ const explore_stdin_session_1 = require("./explore-stdin-session");
24
25
  const workspace_resolution_1 = require("./workspace-resolution");
25
26
  const workspace_manager_1 = require("./workspace-manager");
26
27
  const blueprint_render_1 = require("./blueprint-render");
28
+ const explore_draft_title_1 = require("./explore-draft-title");
27
29
  const COMMAND_INSTRUCTION = 'When you want to suggest a SpecRails command for the user to execute, wrap it in a command block like this: ' +
28
30
  ':::command\n/specrails:implement #42\n::: ' +
29
31
  'The user will be prompted to confirm before the command runs.';
@@ -607,6 +609,12 @@ class ChatManager {
607
609
  const successRate = stats.totalJobs > 0
608
610
  ? Math.round(((stats.totalJobs - stats.failedJobs) / stats.totalJobs) * 100)
609
611
  : null;
612
+ const totalCost = stats.unpricedRuns > 0 && stats.pricedRuns === 0
613
+ ? 'unavailable'
614
+ : `${stats.unpricedRuns > 0 ? 'at least ' : ''}$${stats.totalCostUsd.toFixed(3)}`;
615
+ const todayCost = stats.unpricedTodayRuns > 0 && stats.pricedTodayRuns === 0
616
+ ? 'unavailable'
617
+ : `${stats.unpricedTodayRuns > 0 ? 'at least ' : ''}$${stats.costToday.toFixed(3)}`;
610
618
  return (`## Current Dashboard Context\n\n` +
611
619
  `### Active Job\n${activeLine}\n\n` +
612
620
  (jobLines.length > 0 ? `### Recent Jobs\n${jobLines.join('\n')}\n\n` : '') +
@@ -614,8 +622,8 @@ class ChatManager {
614
622
  `- Total jobs: ${stats.totalJobs}\n` +
615
623
  `- Jobs today: ${stats.jobsToday}\n` +
616
624
  (successRate != null ? `- Overall success rate: ${successRate}%\n` : '') +
617
- `- Total cost: $${stats.totalCostUsd.toFixed(3)}\n` +
618
- `- Cost today: $${stats.costToday.toFixed(3)}`);
625
+ `- Total cost: ${totalCost}\n` +
626
+ `- Cost today: ${todayCost}`);
619
627
  }
620
628
  catch {
621
629
  // Context is best-effort; fall back gracefully
@@ -781,6 +789,15 @@ class ChatManager {
781
789
  // provider wins; null/legacy conversations fall back to the project
782
790
  // primary (this._adapter). Resolved once and used for the whole turn.
783
791
  const adapter = this._adapterForConversation(conversation);
792
+ if (conversation.kind === 'milestone' && !(0, runtime_1.supportsToolPolicy)(adapter, 'read-only')) {
793
+ this._broadcast({
794
+ type: 'chat_error',
795
+ conversationId,
796
+ error: 'provider_tool_policy_unsupported',
797
+ timestamp: new Date().toISOString(),
798
+ });
799
+ return;
800
+ }
784
801
  if (!(0, binary_probe_1.binaryOnPath)(adapter.binary)) {
785
802
  this._broadcast({
786
803
  type: 'chat_error',
@@ -824,19 +841,57 @@ class ChatManager {
824
841
  role: 'user',
825
842
  content: userText,
826
843
  });
827
- // Resolve slash commands (e.g. /specrails:propose-spec → prompt content)
828
- let resolvedText = (0, command_resolver_1.resolveCommand)(userText, this._cwd ?? process.cwd());
844
+ const lightweight = options?.lightweight ?? false;
845
+ const conversationScope = this._resolveConversationScope(conversation);
846
+ const spawnCwd = this._resolveSpawnCwd(conversation.kind, conversationScope, adapter.id);
847
+ // Kimi print mode does not run the TUI slash-command interceptor. Resolve
848
+ // its project skill before the generic `.claude/commands` fallback can fold
849
+ // provider-incompatible content. Other adapters retain the legacy resolver.
850
+ const isDesktopExploreDraftCommand = /^\/specrails:explore-spec(?:\s|$)/.test(userText);
851
+ let resolvedText;
852
+ try {
853
+ resolvedText = adapter.capabilities.materializeHeadlessSkills
854
+ && !isDesktopExploreDraftCommand
855
+ ? (0, runtime_1.formatProviderCommand)(adapter, userText, spawnCwd, conversation.session_id ?? undefined)
856
+ : (0, command_resolver_1.resolveCommand)(userText, this._cwd ?? process.cwd());
857
+ }
858
+ catch (err) {
859
+ // Headless skill resolution is deliberately fail-closed (sending the
860
+ // unresolved slash text to Kimi print mode would silently do the wrong
861
+ // thing), but this happens after an Explore slot is claimed. Surface an
862
+ // actionable chat error and release the slot just like a terminal child
863
+ // instead of leaving the conversation permanently marked as streaming.
864
+ if (conversation.kind === 'explore') {
865
+ const life = this._exploreLifecycle.get(conversationId);
866
+ if (life) {
867
+ life.isStreaming = false;
868
+ life.lastActivityAt = Date.now();
869
+ if (life.isMinimized)
870
+ this._startIdleTimer(conversationId);
871
+ }
872
+ this._drainExploreQueue();
873
+ }
874
+ this._broadcast({
875
+ type: 'chat_error',
876
+ conversationId,
877
+ error: err instanceof Error ? err.message : String(err),
878
+ timestamp: new Date().toISOString(),
879
+ });
880
+ return;
881
+ }
829
882
  // Fold attachments into the prompt as <user-attachment> text blocks under
830
883
  // an "## Attached Resources" section, mirroring how /generate-spec wires
831
884
  // them. Errors during extraction are logged and skipped — the chat turn
832
885
  // proceeds without that attachment rather than failing.
833
886
  let hasAttachments = false;
887
+ let attachmentImagePaths = [];
834
888
  if (options?.attachments && options.attachments.ids.length > 0) {
835
889
  try {
836
890
  const attachmentResult = await this._awaitWhileLive(attachment_manager_1.attachmentManager.getClaudeArgs(options.attachments.slug, options.attachments.ticketKey, options.attachments.ids));
837
891
  if (attachmentResult.disposed)
838
892
  return;
839
- const { textBlocks } = attachmentResult.value;
893
+ const { textBlocks, imagePaths } = attachmentResult.value;
894
+ attachmentImagePaths = imagePaths;
840
895
  if (textBlocks.length > 0) {
841
896
  resolvedText = `${resolvedText}\n\n## Attached Resources\n\n${textBlocks.join('\n\n')}`;
842
897
  hasAttachments = true;
@@ -853,8 +908,6 @@ class ChatManager {
853
908
  // Build spawn args via the resolved adapter. System prompt placement
854
909
  // (--system-prompt flag vs prompt-fold) and resume vs fresh-turn are both
855
910
  // adapter-driven via capability flags.
856
- const lightweight = options?.lightweight ?? false;
857
- const conversationScope = this._resolveConversationScope(conversation);
858
911
  let systemPrompt = conversation.kind === 'milestone'
859
912
  ? this._buildMilestoneSystemPrompt(conversation)
860
913
  : lightweight
@@ -863,7 +916,10 @@ class ChatManager {
863
916
  if (hasAttachments)
864
917
  systemPrompt = `${systemPrompt}\n\n${attachment_manager_1.USER_ATTACHMENT_SYSTEM_NOTE}`;
865
918
  const binary = adapter.binary;
866
- const model = conversation.model || adapter.defaultModel();
919
+ const requestedModel = conversation.model;
920
+ const model = requestedModel && (0, providers_1.isModelAvailableForAdapter)(adapter, requestedModel)
921
+ ? requestedModel
922
+ : adapter.defaultModel();
867
923
  const action = conversation.session_id && adapter.capabilities.nativeResume
868
924
  ? 'chat-resume'
869
925
  : 'chat-turn';
@@ -921,23 +977,34 @@ class ChatManager {
921
977
  `## User turn\n\n${resolvedText}`;
922
978
  }
923
979
  }
980
+ const foldProjectChatSystemPrompt = !adapter.capabilities.systemPromptArg &&
981
+ adapter.capabilities.foldProjectChatSystemPrompt === true;
924
982
  // Sidebar turns: the volatile dashboard snapshot lives in the user turn
925
983
  // (not --system-prompt) so the cacheable system-prompt prefix stays
926
984
  // byte-stable across turns — same pattern as the codex scoped-context
927
- // prepend above. Gated on systemPromptArg: adapters without it (codex)
928
- // drop the system prompt for chat turns entirely (argv stays
929
- // user-text-only by design), so they never saw the dashboard block and
930
- // must not start receiving it here.
931
- if (!lightweight && adapter.capabilities.systemPromptArg) {
985
+ // prepend above. Kimi explicitly requests the same dynamic context through
986
+ // a folded prompt; Codex/Gemini keep their cwd-instruction-file contract.
987
+ if (!lightweight && (adapter.capabilities.systemPromptArg || foldProjectChatSystemPrompt)) {
932
988
  const dashboardContext = this._buildDashboardContextBlock();
933
989
  if (dashboardContext) {
934
990
  promptForAdapter = `${dashboardContext}\n\n## User turn\n\n${promptForAdapter}`;
935
991
  }
936
992
  }
993
+ // Kimi prompt mode has no native system-prompt argument. Fold the stable
994
+ // sidebar contract exactly once here, after adding volatile dashboard
995
+ // context. Explore is intentionally excluded: its isolated cwd already
996
+ // contains provider-native AGENTS.md framing, while the scoped project
997
+ // context above remains per-turn. Milestone already folded its complete
998
+ // contract earlier in this function.
999
+ if (foldProjectChatSystemPrompt &&
1000
+ conversation.kind !== 'explore' &&
1001
+ conversation.kind !== 'milestone') {
1002
+ promptForAdapter = `${systemPrompt}\n\n---\n\n${promptForAdapter}`;
1003
+ }
937
1004
  const buildRecoveryPrompt = () => buildResumeRecoveryPrompt((0, db_1.getMessages)(this._db, conversationId)
938
1005
  .filter((message) => message.id !== persistedUserMessage.id)
939
1006
  .map((message) => ({ role: message.role, content: message.content })), promptForAdapter);
940
- let args = adapter.buildArgs(action, {
1007
+ const turnOptions = {
941
1008
  prompt: promptForAdapter,
942
1009
  systemPrompt,
943
1010
  model,
@@ -952,14 +1019,15 @@ class ChatManager {
952
1019
  // plugin, and connector MCP servers — which require the `user` setting
953
1020
  // source. Claude-only (codex reads ~/.codex natively, ignores this).
954
1021
  loadUserEnv: adapter.id === 'claude' && !!conversationScope?.userMcp,
955
- });
1022
+ imagePaths: adapter.capabilities.supportsImageInput ? attachmentImagePaths : undefined,
1023
+ };
1024
+ let args = adapter.buildArgs(action, turnOptions);
956
1025
  if (conversationScope) {
957
1026
  console.log(`[chat-manager] scope=${JSON.stringify(conversationScope)} flags=${scopeFlags.join(' ')} promptBytes=${Buffer.byteLength(systemPrompt)}`);
958
1027
  }
959
1028
  // No OTEL env injection here — ChatManager spawns are interactive user sessions,
960
1029
  // not pipeline jobs. Telemetry is scoped to QueueManager pipeline runs only.
961
1030
  // spawnAiCli reroutes multi-line argv values through stdin on Windows.
962
- const spawnCwd = this._resolveSpawnCwd(conversation.kind, conversationScope, adapter.id);
963
1031
  // Relocate-artifacts env for gated spawns: NON-explore (sidebar) turns, and
964
1032
  // Explore turns with `contextScope.mcp` (they spawn from the workspace when
965
1033
  // relocated — see `_resolveSpawnCwd`; the env makes the workspace's
@@ -979,6 +1047,7 @@ class ChatManager {
979
1047
  spawnEnv = { ...spawnEnv, GEMINI_CLI_TRUST_WORKSPACE: 'true' };
980
1048
  }
981
1049
  }
1050
+ spawnEnv = (0, runtime_1.buildProviderEnv)(adapter, turnOptions, spawnEnv);
982
1051
  const allowMissingSessionRecovery = adapter.id === 'claude' &&
983
1052
  conversation.kind === 'explore' &&
984
1053
  exploreMcp &&
@@ -1085,42 +1154,30 @@ class ChatManager {
1085
1154
  const readerHandler = (line) => {
1086
1155
  if (this._disposed)
1087
1156
  return;
1088
- const ev = adapter.parseStreamLine(line);
1089
- if (!ev)
1090
- return;
1091
- adapterEvents.push(ev);
1092
- switch (ev.kind) {
1093
- case 'text-delta':
1094
- emitDelta(ev.text);
1095
- break;
1096
- case 'session-started':
1097
- // Last-wins: Claude rotates session ids across --resume, and only the
1098
- // id present at result-time is persisted on disk. Capturing the first
1099
- // one leaves DB with a ghost id that fails the next --resume.
1100
- if (ev.sessionId)
1101
- capturedSessionId = ev.sessionId;
1102
- break;
1103
- case 'result':
1104
- sawResult = true;
1105
- // Claude's result event carries the canonical (post-rotation)
1106
- // session_id; codex captures from thread.started but mirroring here
1107
- // is harmless.
1108
- {
1157
+ for (const ev of (0, runtime_1.parseStreamEvents)(adapter, line)) {
1158
+ adapterEvents.push(ev);
1159
+ switch (ev.kind) {
1160
+ case 'text-delta':
1161
+ emitDelta(ev.text);
1162
+ break;
1163
+ case 'session-started':
1164
+ if (ev.sessionId)
1165
+ capturedSessionId = ev.sessionId;
1166
+ break;
1167
+ case 'result': {
1168
+ sawResult = true;
1109
1169
  const sid = ev.payload.session_id;
1110
1170
  if (sid)
1111
1171
  capturedSessionId = sid;
1172
+ break;
1112
1173
  }
1113
- break;
1114
- case 'error':
1115
- // Capture the provider's explicit failure reason. Last-wins (codex
1116
- // emits `error` then `turn.failed`, both carrying the same message).
1117
- capturedErrorMessage = ev.message;
1118
- break;
1119
- case 'tool-use':
1120
- case 'other':
1121
- // No-op for ChatManager — adapter parses tool_use into the unified
1122
- // event shape but the chat UI does not currently surface them.
1123
- break;
1174
+ case 'error':
1175
+ capturedErrorMessage = ev.message;
1176
+ break;
1177
+ case 'tool-use':
1178
+ case 'other':
1179
+ break;
1180
+ }
1124
1181
  }
1125
1182
  };
1126
1183
  stdoutReader.on('line', readerHandler);
@@ -1440,7 +1497,7 @@ class ChatManager {
1440
1497
  // AND sidebar (surface='chat-sidebar') turns — see _recordChatInvocation.
1441
1498
  const invStatus = wasAborting
1442
1499
  ? 'aborted'
1443
- : code === 0
1500
+ : code === 0 && !capturedErrorMessage
1444
1501
  ? 'success'
1445
1502
  : 'failed';
1446
1503
  this._recordChatInvocation({
@@ -1457,7 +1514,7 @@ class ChatManager {
1457
1514
  complete();
1458
1515
  return;
1459
1516
  }
1460
- if (code === 0) {
1517
+ if (code === 0 && !capturedErrorMessage) {
1461
1518
  // Parse out any spec-draft fenced blocks (Explore Spec protocol).
1462
1519
  // No-op for non-Explore conversations (parser pre-checks for the fence
1463
1520
  // marker and returns the original text unchanged).
@@ -1603,8 +1660,10 @@ class ChatManager {
1603
1660
  if (!this._projectId)
1604
1661
  return;
1605
1662
  try {
1663
+ const finishedAt = new Date().toISOString();
1606
1664
  const { result, estimated } = (0, result_event_1.finaliseInvocationResult)(adapter, adapterEvents, {
1607
1665
  fallbackModel: model,
1666
+ durationMs: Math.max(0, Date.parse(finishedAt) - Date.parse(turnStartedAt)),
1608
1667
  });
1609
1668
  // MED-1: the persistent-stdin transport reuses ONE long-lived child for
1610
1669
  // the whole session, so claude's `result` event reports
@@ -1646,7 +1705,7 @@ class ChatManager {
1646
1705
  conversation_id: conversationId,
1647
1706
  status,
1648
1707
  started_at: turnStartedAt,
1649
- finished_at: new Date().toISOString(),
1708
+ finished_at: finishedAt,
1650
1709
  total_cost_usd_estimated: estimated,
1651
1710
  // Non-cumulative fields (model, durations, session_id) pass through.
1652
1711
  ...result,
@@ -1843,33 +1902,31 @@ class ChatManager {
1843
1902
  const onLine = (line) => {
1844
1903
  if (this._disposed || settled)
1845
1904
  return;
1846
- const ev = adapter.parseStreamLine(line);
1847
- if (!ev)
1848
- return;
1849
- adapterEvents.push(ev);
1850
- switch (ev.kind) {
1851
- case 'text-delta':
1852
- emitDelta(ev.text);
1853
- break;
1854
- case 'session-started':
1855
- if (ev.sessionId)
1856
- capturedSessionId = ev.sessionId;
1857
- break;
1858
- case 'result': {
1859
- const payload = ev.payload;
1860
- if (recoverMissingSession(isMissingClaudeSessionErrorResult(payload) ? payload : null))
1905
+ for (const ev of (0, runtime_1.parseStreamEvents)(adapter, line)) {
1906
+ adapterEvents.push(ev);
1907
+ switch (ev.kind) {
1908
+ case 'text-delta':
1909
+ emitDelta(ev.text);
1910
+ break;
1911
+ case 'session-started':
1912
+ if (ev.sessionId)
1913
+ capturedSessionId = ev.sessionId;
1914
+ break;
1915
+ case 'result': {
1916
+ const payload = ev.payload;
1917
+ if (recoverMissingSession(isMissingClaudeSessionErrorResult(payload) ? payload : null))
1918
+ break;
1919
+ if (payload.session_id)
1920
+ capturedSessionId = payload.session_id;
1921
+ resultIsError =
1922
+ payload.is_error === true ||
1923
+ (typeof payload.subtype === 'string' && payload.subtype.startsWith('error'));
1924
+ finishTurn();
1925
+ break;
1926
+ }
1927
+ default:
1861
1928
  break;
1862
- if (payload.session_id)
1863
- capturedSessionId = payload.session_id;
1864
- // LOW-7: detect a failed turn reported through the result frame.
1865
- resultIsError =
1866
- payload.is_error === true ||
1867
- (typeof payload.subtype === 'string' && payload.subtype.startsWith('error'));
1868
- finishTurn();
1869
- break;
1870
1929
  }
1871
- default:
1872
- break;
1873
1930
  }
1874
1931
  };
1875
1932
  this._stdinSessions.setHandlers(conversationId, {
@@ -2022,8 +2079,10 @@ class ChatManager {
2022
2079
  // 'explore-spec' — no new surface value (analytics guardrail).
2023
2080
  const surface = opts.kind === 'explore' || opts.kind === 'milestone' ? 'explore-spec' : 'chat-sidebar';
2024
2081
  try {
2082
+ const finishedAt = new Date().toISOString();
2025
2083
  const { result, estimated } = (0, result_event_1.finaliseInvocationResult)(opts.adapter, opts.events, {
2026
2084
  fallbackModel: opts.model,
2085
+ durationMs: Math.max(0, Date.parse(finishedAt) - Date.parse(opts.startedAt)),
2027
2086
  });
2028
2087
  (0, ai_invocations_1.recordInvocation)(this._db, {
2029
2088
  id: (0, crypto_1.randomUUID)(),
@@ -2034,7 +2093,7 @@ class ChatManager {
2034
2093
  conversation_id: opts.conversationId,
2035
2094
  status: opts.status,
2036
2095
  started_at: opts.startedAt,
2037
- finished_at: new Date().toISOString(),
2096
+ finished_at: finishedAt,
2038
2097
  total_cost_usd_estimated: estimated,
2039
2098
  ...result,
2040
2099
  });
@@ -2051,14 +2110,33 @@ class ChatManager {
2051
2110
  // Title generation runs on the conversation's own provider.
2052
2111
  const conv = (0, db_1.getConversation)(this._db, conversationId);
2053
2112
  const adapter = this._adapterForConversation(conv ?? {});
2113
+ const toolPolicy = (0, runtime_1.pureOutputToolPolicy)(adapter);
2114
+ if (!toolPolicy) {
2115
+ // Kimi `-p` forces automatic tool approval, so it cannot safely run a
2116
+ // pure-output title turn. Preserve the user-facing feature with the
2117
+ // same deterministic, local fallback used by Agent Chat.
2118
+ const title = (0, explore_draft_title_1.generateAutoTitle)([{ role: 'user', content: firstUserMsg }]);
2119
+ if (title) {
2120
+ (0, db_1.updateConversation)(this._db, conversationId, { title });
2121
+ this._broadcast({
2122
+ type: 'chat_title_update',
2123
+ conversationId,
2124
+ title,
2125
+ timestamp: new Date().toISOString(),
2126
+ });
2127
+ }
2128
+ return;
2129
+ }
2054
2130
  const titlePrompt = `Generate a 4-6 word title for this conversation. Output ONLY the title text, no quotes or punctuation.\n\n` +
2055
2131
  `User: ${firstUserMsg.slice(0, 200)}\nAssistant: ${firstResponse.slice(0, 300)}`;
2056
- const args = adapter.buildArgs('auto-title', {
2132
+ const titleOptions = {
2057
2133
  prompt: titlePrompt,
2058
2134
  model: adapter.defaultModel(),
2059
- });
2135
+ toolPolicy,
2136
+ };
2137
+ const args = adapter.buildArgs('auto-title', titleOptions);
2060
2138
  const child = this._spawnOwned(adapter.binary, args, {
2061
- env: process.env,
2139
+ env: (0, runtime_1.buildProviderEnv)(adapter, titleOptions),
2062
2140
  stdio: ['ignore', 'pipe', 'pipe'],
2063
2141
  cwd: this._cwd,
2064
2142
  });
@@ -2078,15 +2156,13 @@ class ChatManager {
2078
2156
  reader.on('line', (line) => {
2079
2157
  if (this._disposed)
2080
2158
  return;
2081
- const ev = adapter.parseStreamLine(line);
2082
- if (!ev)
2083
- return;
2084
- titleEvents.push(ev);
2085
- // Keep the FIRST non-empty text-delta as the title (unchanged behaviour).
2086
- if (!titleText && ev.kind === 'text-delta') {
2087
- const trimmed = ev.text.trim();
2088
- if (trimmed)
2089
- titleText = trimmed;
2159
+ for (const ev of (0, runtime_1.parseStreamEvents)(adapter, line)) {
2160
+ titleEvents.push(ev);
2161
+ if (!titleText && ev.kind === 'text-delta') {
2162
+ const trimmed = ev.text.trim();
2163
+ if (trimmed)
2164
+ titleText = trimmed;
2165
+ }
2090
2166
  }
2091
2167
  });
2092
2168
  child.on('error', () => {
@@ -13,6 +13,7 @@ const build_dirs_1 = require("./build-dirs");
13
13
  const file_provenance_1 = require("./file-provenance");
14
14
  const file_summary_manager_1 = require("./file-summary-manager");
15
15
  const file_story_1 = require("./file-story");
16
+ const providers_1 = require("./providers");
16
17
  const MAX_TREE_PAGE = 2000;
17
18
  const MAX_FILE_BYTES = 2 * 1024 * 1024;
18
19
  const BINARY_PROBE_BYTES = 8 * 1024;
@@ -346,6 +347,18 @@ function createCodeExplorerRouter(deps) {
346
347
  const listByTicket = deps.listProvenanceByTicket ?? file_provenance_1.listProvenanceByTicket;
347
348
  // Summary OUTPUT root (workspace when relocated). Source reads use projectPath.
348
349
  const summaryRoot = () => deps.resolveSummaryRoot?.() ?? deps.projectPath;
350
+ const aiTransformProvider = deps.aiTransformProvider;
351
+ const aiTransformsAvailable = (0, providers_1.pureOutputToolPolicy)((0, providers_1.getAdapter)(aiTransformProvider)) !== null;
352
+ function requireAiTransforms(res) {
353
+ if (aiTransformsAvailable)
354
+ return true;
355
+ res.status(409).json({
356
+ error: 'provider_tool_policy_unsupported',
357
+ provider: aiTransformProvider,
358
+ requiredPolicy: 'pure-output',
359
+ });
360
+ return false;
361
+ }
349
362
  // Short-TTL per-project cache so paginating a large `all` tree reuses ONE
350
363
  // synchronous filesystem walk instead of re-walking (and re-statting) on every
351
364
  // page — the cursor only slices an already-materialized array. Also caches the
@@ -375,14 +388,16 @@ function createCodeExplorerRouter(deps) {
375
388
  res.status(404).end();
376
389
  return;
377
390
  }
378
- // Lazily attach the file-summary watcher on first Code-Explorer use. It is
379
- // not attached at registry load (that recursive watcher caused the fd leak
380
- // that broke terminals); attachWatcher is idempotent, so this is cheap on
381
- // every subsequent request.
382
- try {
383
- deps.fileSummaryManager.attachWatcher(deps.projectId, deps.projectPath, summaryRoot());
391
+ // Lazily attach the file-summary watcher on first Code-Explorer use. A
392
+ // provider without a native pure-output boundary must not attach it:
393
+ // implicit file-change work and explicit POSTs both fail closed before a
394
+ // manager or provider process can run.
395
+ if (aiTransformsAvailable) {
396
+ try {
397
+ deps.fileSummaryManager.attachWatcher(deps.projectId, deps.projectPath, summaryRoot());
398
+ }
399
+ catch { /* non-fatal */ }
384
400
  }
385
- catch { /* non-fatal */ }
386
401
  next();
387
402
  });
388
403
  router.get('/tree', (req, res) => {
@@ -694,6 +709,8 @@ function createCodeExplorerRouter(deps) {
694
709
  // turn; the file.story_updated WS event fires too so other open viewers of
695
710
  // the same file refresh.
696
711
  router.post('/file/story/explain', async (req, res) => {
712
+ if (!requireAiTransforms(res))
713
+ return;
697
714
  const relRaw = req.query.path;
698
715
  if (!relRaw || typeof relRaw !== 'string') {
699
716
  res.status(400).json({ error: 'path query parameter is required' });
@@ -754,6 +771,8 @@ function createCodeExplorerRouter(deps) {
754
771
  }
755
772
  });
756
773
  router.post('/file/regenerate-summary', async (req, res) => {
774
+ if (!requireAiTransforms(res))
775
+ return;
757
776
  const relRaw = req.query.path;
758
777
  if (!relRaw || typeof relRaw !== 'string') {
759
778
  res.status(400).json({ error: 'path query parameter is required' });