specrails-desktop 2.30.3 → 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 (460) hide show
  1. package/README.md +28 -24
  2. package/client/dist/assets/{ActivityFeedPage-CcBOllqm.js → ActivityFeedPage-CAhFRUO4.js} +1 -1
  3. package/client/dist/assets/{AgentBrowserCapture-BOJkZUj_.js → AgentBrowserCapture-Ct4G5lsE.js} +1 -1
  4. package/client/dist/assets/AgentModeCodePane-DQt9WIvu.js +2 -0
  5. package/client/dist/assets/{AgentModeJobsPane-C5tkU2Ed.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-DCmgYV86.js → DesktopAnalyticsPage-Dnf99E8e.js} +1 -1
  10. package/client/dist/assets/{DocsDialog-BXnOgm7V.js → DocsDialog-DHi36rZ4.js} +1 -1
  11. package/client/dist/assets/{DocsPage-nFkHh-Dc.js → DocsPage-DIdVrQz1.js} +1 -1
  12. package/client/dist/assets/{InteractiveJobComposer-qoeZjwVY.js → InteractiveJobComposer-BHeB2inv.js} +1 -1
  13. package/client/dist/assets/{JobDetailModal-ChRBB9bc.js → JobDetailModal-CF1SKUHH.js} +1 -1
  14. package/client/dist/assets/JobDetailPage-De3rY18h.js +1 -0
  15. package/client/dist/assets/{JobsPage-C_UEIXWA.js → JobsPage-CfAqqJTx.js} +1 -1
  16. package/client/dist/assets/{LoopBuilderPage-BV_wge0g.js → LoopBuilderPage-DSBMuTb3.js} +2 -2
  17. package/client/dist/assets/{LoopPreviewModal-Qk_Jaxo8.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-DEHcyt_I.js → ProjectSettingsDialog-DA23PSi6.js} +1 -1
  22. package/client/dist/assets/{TemplatePreviewModal-Bxbtnzob.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-CBhuoTO6.js → dist-js-BaP-xLOy.js} +1 -1
  90. package/client/dist/assets/{dist-js-eSxaoEw0.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/db.js +60 -5
  298. package/server/dist/desktop-analytics.js +44 -6
  299. package/server/dist/desktop-router.js +127 -11
  300. package/server/dist/explore-cwd-manager.js +19 -3
  301. package/server/dist/feature-flags.js +1 -1
  302. package/server/dist/file-story-manager.js +3 -3
  303. package/server/dist/file-summary-generator.js +21 -15
  304. package/server/dist/file-summary-manager.js +6 -6
  305. package/server/dist/framework-migration.js +27 -12
  306. package/server/dist/install-config-path.js +1 -1
  307. package/server/dist/interactive-job-session.js +8 -6
  308. package/server/dist/loop-command-catalog.js +27 -10
  309. package/server/dist/loop-executors.js +59 -27
  310. package/server/dist/loop-factory.js +2 -2
  311. package/server/dist/loop-run-manager.js +100 -39
  312. package/server/dist/loops-router.js +1 -1
  313. package/server/dist/mcp/guide.js +23 -16
  314. package/server/dist/mcp/tools/agents.js +47 -41
  315. package/server/dist/mcp/tools/analytics.js +2 -2
  316. package/server/dist/mcp/tools/loops.js +2 -2
  317. package/server/dist/mcp/tools/plugins.js +14 -9
  318. package/server/dist/mcp/tools/rails.js +12 -13
  319. package/server/dist/mcp/tools/support.js +1 -1
  320. package/server/dist/mobile/mobile-router.js +5 -1
  321. package/server/dist/offline-assemble.js +13 -1
  322. package/server/dist/path-resolver.js +2 -2
  323. package/server/dist/plugin-manager.js +391 -106
  324. package/server/dist/plugins/claude-md-mutation.js +5 -4
  325. package/server/dist/plugins/drift.js +32 -29
  326. package/server/dist/plugins/rail-integration.js +13 -2
  327. package/server/dist/plugins/serena/install.js +25 -17
  328. package/server/dist/plugins/serena/manifest.js +22 -5
  329. package/server/dist/plugins/serena/verify.js +46 -16
  330. package/server/dist/plugins-router.js +59 -8
  331. package/server/dist/profile-manager.js +148 -36
  332. package/server/dist/profiles-router.js +311 -82
  333. package/server/dist/project-registry.js +2 -2
  334. package/server/dist/project-router-chat.js +17 -8
  335. package/server/dist/project-router-helpers.js +93 -5
  336. package/server/dist/project-router-jobs.js +76 -12
  337. package/server/dist/project-router-loop-runs.js +42 -14
  338. package/server/dist/project-router-settings.js +31 -10
  339. package/server/dist/project-router-setup.js +4 -5
  340. package/server/dist/project-router-spending.js +15 -3
  341. package/server/dist/project-router-tickets.js +187 -181
  342. package/server/dist/project-router.js +1 -0
  343. package/server/dist/proposal-manager.js +125 -85
  344. package/server/dist/providers/claude-adapter.js +14 -0
  345. package/server/dist/providers/codex-adapter.js +20 -0
  346. package/server/dist/providers/gemini-adapter.js +14 -0
  347. package/server/dist/providers/index.js +18 -1
  348. package/server/dist/providers/kimi-adapter.js +361 -0
  349. package/server/dist/providers/kimi-skill-prompt.js +332 -0
  350. package/server/dist/providers/runtime.js +137 -0
  351. package/server/dist/queue-manager.js +103 -78
  352. package/server/dist/rail-isolated-launch.js +1 -1
  353. package/server/dist/rails-router.js +51 -38
  354. package/server/dist/result-event.js +5 -0
  355. package/server/dist/schemas/profile.v1.json +3 -4
  356. package/server/dist/setup-manager.js +222 -101
  357. package/server/dist/setup-prerequisites.js +7 -1
  358. package/server/dist/smash-runner.js +104 -57
  359. package/server/dist/spawn-lifecycle.js +18 -14
  360. package/server/dist/spec-launcher-manager.js +66 -54
  361. package/server/dist/spec-models.js +15 -6
  362. package/server/dist/spending.js +110 -17
  363. package/server/dist/util/cli-prompt.js +142 -5
  364. package/server/dist/util/stream-display.js +46 -0
  365. package/server/dist/workspace-manager.js +100 -37
  366. package/server/dist/worktree-manager.js +4 -0
  367. package/server/dist/worktree-overlay.js +12 -3
  368. package/client/dist/assets/AgentModeCodePane-BIh67rGi.js +0 -2
  369. package/client/dist/assets/AgentsPage-CNlE_Uh4.js +0 -86
  370. package/client/dist/assets/AnalyticsPage-cYcJxWPn.js +0 -1
  371. package/client/dist/assets/CodePage-D4QYa4t1.js +0 -2
  372. package/client/dist/assets/JobDetailPage-BuOnlndB.js +0 -1
  373. package/client/dist/assets/LoopsPage-lLi6ax0p.js +0 -1
  374. package/client/dist/assets/PluginsPage-DZIFv9kw.js +0 -2
  375. package/client/dist/assets/TicketDetailModal-CqQ5gx7O.js +0 -14
  376. package/client/dist/assets/addspec-BXjvkh5q.js +0 -1
  377. package/client/dist/assets/addspec-C4bwrmpi.js +0 -1
  378. package/client/dist/assets/addspec-CArHXUDM.js +0 -1
  379. package/client/dist/assets/addspec-DTKJcXiR.js +0 -1
  380. package/client/dist/assets/addspec-DbNBbrha.js +0 -1
  381. package/client/dist/assets/addspec-Rw8uVBC1.js +0 -1
  382. package/client/dist/assets/addspec-htHGZHOe.js +0 -1
  383. package/client/dist/assets/addspec-lr_LP6jk.js +0 -1
  384. package/client/dist/assets/agent-BABjRt0P.js +0 -1
  385. package/client/dist/assets/agent-BuUpgqVx.js +0 -1
  386. package/client/dist/assets/agent-CCDZesgU.js +0 -1
  387. package/client/dist/assets/agent-CVn64zWH.js +0 -1
  388. package/client/dist/assets/agent-CWeOny0E.js +0 -1
  389. package/client/dist/assets/agent-DOysmad_.js +0 -1
  390. package/client/dist/assets/agent-DSsjTBhn.js +0 -1
  391. package/client/dist/assets/agent-DlZMxfM9.js +0 -1
  392. package/client/dist/assets/agents-6ZOJXWVk.js +0 -1
  393. package/client/dist/assets/agents-BZ0XQKjg.js +0 -1
  394. package/client/dist/assets/agents-BqWklnHR.js +0 -1
  395. package/client/dist/assets/agents-Bz-thHkB.js +0 -1
  396. package/client/dist/assets/agents-CJclElx6.js +0 -1
  397. package/client/dist/assets/agents-D_l9L0VA.js +0 -1
  398. package/client/dist/assets/agents-DhG7mg6-.js +0 -1
  399. package/client/dist/assets/agents-u_C0jPB5.js +0 -1
  400. package/client/dist/assets/agentstudio-BF19hbXj.js +0 -1
  401. package/client/dist/assets/agentstudio-BIUe1Im2.js +0 -1
  402. package/client/dist/assets/agentstudio-BLGvxWBx.js +0 -1
  403. package/client/dist/assets/agentstudio-D9UKiWTj.js +0 -1
  404. package/client/dist/assets/agentstudio-Drs37nIM.js +0 -1
  405. package/client/dist/assets/agentstudio-nEnKkcXM.js +0 -1
  406. package/client/dist/assets/agentstudio-pyFcSBKy.js +0 -1
  407. package/client/dist/assets/agentstudio-zfTzQh3U.js +0 -1
  408. package/client/dist/assets/analytics-BC7mwR4s.js +0 -1
  409. package/client/dist/assets/analytics-BiNg8edP.js +0 -1
  410. package/client/dist/assets/analytics-C0s_T2GN.js +0 -1
  411. package/client/dist/assets/analytics-CdsAPAwZ.js +0 -1
  412. package/client/dist/assets/analytics-Cr1_q-lC.js +0 -1
  413. package/client/dist/assets/analytics-DZ8Aoq3I.js +0 -1
  414. package/client/dist/assets/analytics-IEsKZvpW.js +0 -1
  415. package/client/dist/assets/analytics-SRPf84OT.js +0 -1
  416. package/client/dist/assets/builder-BH8Fua-g.js +0 -1
  417. package/client/dist/assets/builder-BoV7DgKd.js +0 -1
  418. package/client/dist/assets/builder-Cc_Lf79C.js +0 -1
  419. package/client/dist/assets/builder-CwZ7Bme2.js +0 -1
  420. package/client/dist/assets/builder-CxW-JFsR.js +0 -1
  421. package/client/dist/assets/builder-D94lrV7x.js +0 -1
  422. package/client/dist/assets/builder-cqHpCyGa.js +0 -1
  423. package/client/dist/assets/code-76gS0IdL.js +0 -1
  424. package/client/dist/assets/code-BDXEM4ml.js +0 -1
  425. package/client/dist/assets/code-C1VwxlML.js +0 -1
  426. package/client/dist/assets/code-CnMZyJpr.js +0 -1
  427. package/client/dist/assets/code-D20ef9Cf.js +0 -1
  428. package/client/dist/assets/code-DdOXaWRY.js +0 -1
  429. package/client/dist/assets/code-Duohuf9l.js +0 -1
  430. package/client/dist/assets/code-geHmknB9.js +0 -1
  431. package/client/dist/assets/format-command-Dfa1o2d-.js +0 -1
  432. package/client/dist/assets/index-CewRMo0W.js +0 -76
  433. package/client/dist/assets/index-Dh9i4CnN.css +0 -2
  434. package/client/dist/assets/jobs-BCtGfgUA.js +0 -1
  435. package/client/dist/assets/jobs-BHR_EnDI.js +0 -1
  436. package/client/dist/assets/jobs-BMbZJjJJ.js +0 -1
  437. package/client/dist/assets/jobs-DzSIR8V6.js +0 -1
  438. package/client/dist/assets/nav-BRJBxaGt.js +0 -1
  439. package/client/dist/assets/nav-CAGKv3_F.js +0 -1
  440. package/client/dist/assets/nav-CNskRJKQ.js +0 -1
  441. package/client/dist/assets/nav-DY93irBL.js +0 -1
  442. package/client/dist/assets/nav-DhjvYgqt.js +0 -1
  443. package/client/dist/assets/nav-Gb-r4wop.js +0 -1
  444. package/client/dist/assets/nav-TvumAFD2.js +0 -1
  445. package/client/dist/assets/nav-myFaXRHD.js +0 -1
  446. package/client/dist/assets/settings-B0ypw9uw.js +0 -1
  447. package/client/dist/assets/settings-B9ilOSYO.js +0 -1
  448. package/client/dist/assets/settings-BqlWHRHA.js +0 -1
  449. package/client/dist/assets/settings-D8dlqt4d.js +0 -1
  450. package/client/dist/assets/settings-DAZqjX2a.js +0 -1
  451. package/client/dist/assets/settings-DRh39v1S.js +0 -1
  452. package/client/dist/assets/settings-Mj1OJbfd.js +0 -1
  453. package/client/dist/assets/settings-lRReaSYm.js +0 -1
  454. package/client/dist/assets/setup-3832BJCo.js +0 -1
  455. package/client/dist/assets/setup-B8J-gECe.js +0 -1
  456. package/client/dist/assets/setup-BCIOxVsc.js +0 -1
  457. package/client/dist/assets/setup-BciLj_tU.js +0 -1
  458. package/client/dist/assets/setup-CTkE3cfh.js +0 -1
  459. package/client/dist/assets/setup-ajB4rYm-.js +0 -1
  460. package/client/dist/assets/setup-xFO1wNl0.js +0 -1
@@ -201,10 +201,12 @@ class AgentRefineManager {
201
201
  }
202
202
  fs_1.default.writeFileSync(file, session.draft_body, 'utf8');
203
203
  const maxVersion = this._db
204
- .prepare(`SELECT COALESCE(MAX(version), 0) AS v FROM agent_versions WHERE agent_name = ?`)
205
- .get(session.agent_id).v;
204
+ .prepare(`SELECT COALESCE(MAX(version), 0) AS v FROM agent_versions
205
+ WHERE provider = ? AND agent_name = ?`)
206
+ .get(this._adapter.id, session.agent_id).v;
206
207
  const nextVersion = maxVersion + 1;
207
- this._db.prepare(`INSERT INTO agent_versions (agent_name, version, body, created_at) VALUES (?, ?, ?, ?)`).run(session.agent_id, nextVersion, session.draft_body, Date.now());
208
+ this._db.prepare(`INSERT INTO agent_versions (provider, agent_name, version, body, created_at)
209
+ VALUES (?, ?, ?, ?, ?)`).run(this._adapter.id, session.agent_id, nextVersion, session.draft_body, Date.now());
208
210
  (0, agent_refine_db_1.updateRefineSession)(this._db, opts.refineId, { status: 'applied', phase: 'done' });
209
211
  this._broadcast({
210
212
  type: 'agent_refine_applied',
@@ -230,12 +232,6 @@ class AgentRefineManager {
230
232
  throw new Error('manager_disposed');
231
233
  }
232
234
  _agentFile(agentId) {
233
- // Per-provider on-disk layout:
234
- // claude → <root>/.claude/agents/<agentId>.md
235
- // codex → <root>/.codex/skills/<agentId>/SKILL.md
236
- // Future providers add their own branch via the adapter; the projectDir
237
- // is already provider-aware.
238
- //
239
235
  // Relocate-artifacts: custom-* agents are materialized by core into the
240
236
  // WORKSPACE when the project is relocated (same place the rails load them
241
237
  // from). Reading/writing `<project.path>/<providerDir>/agents` would touch a
@@ -244,15 +240,14 @@ class AgentRefineManager {
244
240
  // rails consume. Legacy projects are byte-identical (root === project.path).
245
241
  const exec = (0, workspace_resolution_1.resolveProjectExecution)({ path: this._projectPath });
246
242
  const root = exec.relocated && exec.workspaceDir ? exec.workspaceDir : this._projectPath;
247
- if (this._adapter.id === 'codex') {
248
- return path_1.default.join(root, this._adapter.projectDirName, 'skills', agentId, 'SKILL.md');
249
- }
250
- return path_1.default.join(root, this._adapter.projectDirName, 'agents', `${agentId}.md`);
243
+ return this._adapter.customRolePath?.(root, agentId)
244
+ ?? path_1.default.join(root, this._adapter.projectDirName, 'agents', `${agentId}.md`);
251
245
  }
252
246
  _currentVersion(agentId) {
253
247
  const row = this._db
254
- .prepare(`SELECT COALESCE(MAX(version), 0) AS v FROM agent_versions WHERE agent_name = ?`)
255
- .get(agentId);
248
+ .prepare(`SELECT COALESCE(MAX(version), 0) AS v FROM agent_versions
249
+ WHERE provider = ? AND agent_name = ?`)
250
+ .get(this._adapter.id, agentId);
256
251
  return row.v;
257
252
  }
258
253
  _appendHistory(refineId, turn) {
@@ -272,12 +267,22 @@ class AgentRefineManager {
272
267
  agentId: session.agent_id,
273
268
  currentBody: bodyForFirstTurn ?? '',
274
269
  userInstruction: instruction,
270
+ providerId: this._adapter.id,
275
271
  })
276
272
  : instruction;
277
273
  const action = !isFirst && session.session_id && this._adapter.capabilities.nativeResume
278
274
  ? 'chat-resume'
279
275
  : 'agent-refine';
280
276
  const refineModel = this._adapter.defaultModel();
277
+ const supportedToolPolicies = this._adapter.capabilities.toolPolicies ?? [];
278
+ const toolPolicy = supportedToolPolicies.includes('none')
279
+ ? 'none'
280
+ : supportedToolPolicies.includes('read-only')
281
+ ? 'read-only'
282
+ : null;
283
+ if (!toolPolicy) {
284
+ throw new Error(`provider_tool_policy_unsupported:${this._adapter.id}`);
285
+ }
281
286
  const buildOpts = {
282
287
  prompt,
283
288
  model: refineModel,
@@ -285,9 +290,8 @@ class AgentRefineManager {
285
290
  // The model receives the complete current agent body in its prompt and
286
291
  // must only return a replacement body. Repo tools cannot improve this
287
292
  // contract and would turn prompt text into filesystem authority.
288
- toolPolicy: 'none',
293
+ toolPolicy,
289
294
  };
290
- const args = this._adapter.buildArgs(action, buildOpts);
291
295
  let drafted = false;
292
296
  this._bodyBuffers.set(refineId, '');
293
297
  const turnStartedAt = new Date().toISOString();
@@ -300,14 +304,13 @@ class AgentRefineManager {
300
304
  // Relocate-artifacts gate: spawn from the workspace (with SPECRAILS_REPO_DIR)
301
305
  // when relocated, else cwd = project.path + empty env (byte-identical legacy).
302
306
  const refineExec = (0, workspace_resolution_1.resolveProjectExecution)({ path: this._projectPath });
303
- const refineEnv = refineExec.relocated ? { ...process.env, ...refineExec.env } : undefined;
307
+ const refineEnv = refineExec.relocated ? { ...process.env, ...refineExec.env } : process.env;
304
308
  const run = await (0, spawn_lifecycle_1.runAiCliInvocation)({
305
309
  adapter: this._adapter,
306
- // Pass the already-policy-bound argv so first turns and native resumes
307
- // cannot diverge if the lifecycle rebuilds options later.
308
- argv: args,
310
+ action,
311
+ buildOpts,
309
312
  cwd: refineExec.cwd,
310
- env: refineEnv,
313
+ env: (0, providers_1.buildProviderEnv)(this._adapter, buildOpts, refineEnv),
311
314
  onSpawn: (child) => this._activeProcesses.set(refineId, child),
312
315
  onSpawnError: (err) => { spawnState.err = err; },
313
316
  onEvent: (ev) => {
@@ -369,7 +372,7 @@ class AgentRefineManager {
369
372
  return; // cancel (BUG-LONGTAIL-01)
370
373
  }
371
374
  if (run.spawnFailed) {
372
- this._emitError(refineId, `Failed to launch claude: ${spawnState.err?.message ?? 'spawn error'}`);
375
+ this._emitError(refineId, `Failed to launch ${this._adapter.binary}: ${spawnState.err?.message ?? 'spawn error'}`);
373
376
  (0, agent_refine_db_1.updateRefineSession)(this._db, refineId, { status: 'error', phase: 'idle' });
374
377
  return;
375
378
  }
@@ -377,13 +380,16 @@ class AgentRefineManager {
377
380
  const adapterEvents = run.events;
378
381
  const code = run.code;
379
382
  const stderr = run.stderrTail;
383
+ const providerError = adapterEvents.find((event) => event.kind === 'error')?.message;
380
384
  // ai_invocations capture (surface='ai-edit'). One row per refine turn.
381
- const invStatus = code === 0 && fullDraft.trim() ? 'success' : 'failed';
385
+ const invStatus = code === 0 && !providerError && fullDraft.trim() ? 'success' : 'failed';
382
386
  this._recordRefineInvocation(refineId, adapterEvents, refineModel, invStatus, turnStartedAt);
383
- if (code !== 0 || !fullDraft.trim()) {
384
- this._emitError(refineId, code !== 0
385
- ? `claude exited with code ${code}${stderr ? `: ${stderr.slice(-300)}` : ''}`
386
- : 'claude returned empty output');
387
+ if (code !== 0 || providerError || !fullDraft.trim()) {
388
+ this._emitError(refineId, providerError
389
+ ? providerError
390
+ : code !== 0
391
+ ? `${this._adapter.binary} exited with code ${code}${stderr ? `: ${stderr.slice(-300)}` : ''}`
392
+ : `${this._adapter.binary} returned empty output`);
387
393
  (0, agent_refine_db_1.updateRefineSession)(this._db, refineId, { status: 'error', phase: 'idle' });
388
394
  return;
389
395
  }
@@ -442,7 +448,11 @@ class AgentRefineManager {
442
448
  if (!this._projectId)
443
449
  return;
444
450
  try {
445
- const { result: normalised, estimated } = (0, result_event_1.finaliseInvocationResult)(this._adapter, adapterEvents, { fallbackModel: model });
451
+ const finishedAt = new Date().toISOString();
452
+ const { result: normalised, estimated } = (0, result_event_1.finaliseInvocationResult)(this._adapter, adapterEvents, {
453
+ fallbackModel: model,
454
+ durationMs: Math.max(0, Date.parse(finishedAt) - Date.parse(startedAt)),
455
+ });
446
456
  (0, ai_invocations_1.recordInvocation)(this._db, {
447
457
  id: (0, crypto_1.randomUUID)(),
448
458
  project_id: this._projectId,
@@ -451,7 +461,7 @@ class AgentRefineManager {
451
461
  surface_ref_id: refineId,
452
462
  status,
453
463
  started_at: startedAt,
454
- finished_at: new Date().toISOString(),
464
+ finished_at: finishedAt,
455
465
  total_cost_usd_estimated: estimated,
456
466
  ...normalised,
457
467
  });
@@ -466,11 +476,12 @@ class AgentRefineManager {
466
476
  return;
467
477
  (0, agent_refine_db_1.updateRefineSession)(this._db, refineId, { phase: 'testing' });
468
478
  this._emitPhase(refineId, 'testing');
469
- const sampleTask = pickSampleTask(this._db, agentId);
479
+ const sampleTask = pickSampleTask(this._db, this._adapter.id, agentId);
470
480
  try {
471
481
  const result = await (0, agent_generator_1.testCustomAgent)(this._projectPath, {
472
482
  draftBody,
473
483
  sampleTask,
484
+ providerId: this._adapter.id,
474
485
  // MED-3: the default-on per-refine-turn auto-test is a billable claude
475
486
  // spawn — record it (surface='agent-studio') when we have a project DB.
476
487
  record: this._projectId
@@ -479,8 +490,9 @@ class AgentRefineManager {
479
490
  });
480
491
  if (this._disposed)
481
492
  return;
482
- this._db.prepare(`INSERT INTO agent_tests (agent_name, draft_hash, sample_task_id, tokens, duration_ms, output, created_at)
483
- VALUES (?, ?, ?, ?, ?, ?, ?)`).run(agentId, result.draftHash, null, result.tokens, result.durationMs, result.output, Date.now());
493
+ this._db.prepare(`INSERT INTO agent_tests
494
+ (provider, agent_name, draft_hash, sample_task_id, tokens, duration_ms, output, created_at)
495
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(this._adapter.id, agentId, result.draftHash, null, result.tokens, result.durationMs, result.output, Date.now());
484
496
  (0, agent_refine_db_1.updateRefineSession)(this._db, refineId, {
485
497
  last_test_at: Date.now(),
486
498
  last_test_hash: draftHash,
@@ -564,6 +576,15 @@ function validateAgentBody(body, providerId = 'claude') {
564
576
  return { ok: true };
565
577
  }
566
578
  function buildFirstTurnPrompt(opts) {
579
+ const modelRule = (opts.providerId ?? 'claude') === 'claude'
580
+ ? [
581
+ ' 1. Frontmatter MUST include `name`, `description`, and `model` (one of',
582
+ ' `sonnet`, `opus`, `haiku`).',
583
+ ]
584
+ : [
585
+ ' 1. Frontmatter MUST include `name` and `description`.',
586
+ ' Do not add provider-specific model metadata.',
587
+ ];
567
588
  return [
568
589
  'You are refining an existing custom agent. The user wants to iterate on its',
569
590
  'definition. Output the COMPLETE refined `.md` file — full YAML frontmatter',
@@ -571,8 +592,7 @@ function buildFirstTurnPrompt(opts) {
571
592
  'commentary, or explanations. Start at `---`.',
572
593
  '',
573
594
  'Hard rules:',
574
- ' 1. Frontmatter MUST include `name`, `description`, and `model` (one of',
575
- ' `sonnet`, `opus`, `haiku`).',
595
+ ...modelRule,
576
596
  ` 2. The frontmatter \`name\` MUST remain exactly: ${opts.agentId}`,
577
597
  ' 3. The id `name` is locked; renaming is a separate explicit action.',
578
598
  '',
@@ -589,13 +609,13 @@ function buildFirstTurnPrompt(opts) {
589
609
  'Output only the new file content, starting with `---`.',
590
610
  ].join('\n');
591
611
  }
592
- function pickSampleTask(db, agentId) {
612
+ function pickSampleTask(db, provider, agentId) {
593
613
  try {
594
614
  const row = db
595
615
  .prepare(`SELECT output FROM agent_tests
596
- WHERE agent_name = ? AND sample_task_id IS NOT NULL
616
+ WHERE provider = ? AND agent_name = ? AND sample_task_id IS NOT NULL
597
617
  ORDER BY created_at DESC LIMIT 1`)
598
- .get(agentId);
618
+ .get(provider, agentId);
599
619
  if (row?.output)
600
620
  return row.output;
601
621
  }
@@ -57,44 +57,89 @@ function getTicketSpendingSummary(db, ticketId) {
57
57
  tokens_in, tokens_out, tokens_cache_read, tokens_cache_create
58
58
  FROM ai_invocations WHERE ticket_id = ?`).all(ticketId);
59
59
  const bySurface = {
60
- job: { count: 0, costUsd: 0 },
61
- 'quick-spec': { count: 0, costUsd: 0 },
62
- 'explore-spec': { count: 0, costUsd: 0 },
63
- 'ai-edit': { count: 0, costUsd: 0 },
64
- smash: { count: 0, costUsd: 0 },
65
- 'file-summary': { count: 0, costUsd: 0 },
66
- loop: { count: 0, costUsd: 0 },
67
- 'chat-sidebar': { count: 0, costUsd: 0 },
68
- 'spec-launcher': { count: 0, costUsd: 0 },
69
- proposal: { count: 0, costUsd: 0 },
70
- 'agent-studio': { count: 0, costUsd: 0 },
71
- setup: { count: 0, costUsd: 0 },
60
+ job: { count: 0, costUsd: 0, unpricedCount: 0 },
61
+ 'quick-spec': { count: 0, costUsd: 0, unpricedCount: 0 },
62
+ 'explore-spec': { count: 0, costUsd: 0, unpricedCount: 0 },
63
+ 'ai-edit': { count: 0, costUsd: 0, unpricedCount: 0 },
64
+ smash: { count: 0, costUsd: 0, unpricedCount: 0 },
65
+ 'file-summary': { count: 0, costUsd: 0, unpricedCount: 0 },
66
+ loop: { count: 0, costUsd: 0, unpricedCount: 0 },
67
+ 'chat-sidebar': { count: 0, costUsd: 0, unpricedCount: 0 },
68
+ 'spec-launcher': { count: 0, costUsd: 0, unpricedCount: 0 },
69
+ proposal: { count: 0, costUsd: 0, unpricedCount: 0 },
70
+ 'agent-studio': { count: 0, costUsd: 0, unpricedCount: 0 },
71
+ setup: { count: 0, costUsd: 0, unpricedCount: 0 },
72
72
  };
73
73
  let totalCostUsd = 0;
74
74
  let estimatedCostUsd = 0;
75
75
  let totalTokens = 0;
76
76
  let totalTurns = 0;
77
+ let pricedRuns = 0;
78
+ let unpricedRuns = 0;
79
+ let usageReportedRuns = 0;
80
+ let usageUnavailableRuns = 0;
81
+ let turnsReportedRuns = 0;
82
+ let turnsUnavailableRuns = 0;
77
83
  let activeDurationMs = 0;
78
84
  for (const r of rows) {
79
85
  // Defensive: a row written by a newer schema with a surface this build
80
86
  // doesn't know must not crash the summary.
81
87
  if (!bySurface[r.surface])
82
- bySurface[r.surface] = { count: 0, costUsd: 0 };
88
+ bySurface[r.surface] = { count: 0, costUsd: 0, unpricedCount: 0 };
83
89
  bySurface[r.surface].count += 1;
84
90
  bySurface[r.surface].costUsd += r.total_cost_usd ?? 0;
91
+ if (r.total_cost_usd == null) {
92
+ unpricedRuns += 1;
93
+ bySurface[r.surface].unpricedCount = (bySurface[r.surface].unpricedCount ?? 0) + 1;
94
+ }
95
+ else {
96
+ pricedRuns += 1;
97
+ }
85
98
  totalCostUsd += r.total_cost_usd ?? 0;
86
99
  if (r.total_cost_usd_estimated === 1)
87
100
  estimatedCostUsd += r.total_cost_usd ?? 0;
88
101
  // Real total tokens = fresh input + output + cache-read + cache-create.
89
- totalTokens +=
90
- (r.tokens_in ?? 0) +
91
- (r.tokens_out ?? 0) +
92
- (r.tokens_cache_read ?? 0) +
93
- (r.tokens_cache_create ?? 0);
94
- totalTurns += r.num_turns ?? 0;
102
+ const hasUsage = r.tokens_in != null || r.tokens_out != null
103
+ || r.tokens_cache_read != null || r.tokens_cache_create != null;
104
+ if (hasUsage) {
105
+ usageReportedRuns += 1;
106
+ totalTokens +=
107
+ (r.tokens_in ?? 0) +
108
+ (r.tokens_out ?? 0) +
109
+ (r.tokens_cache_read ?? 0) +
110
+ (r.tokens_cache_create ?? 0);
111
+ }
112
+ else {
113
+ usageUnavailableRuns += 1;
114
+ }
115
+ if (r.num_turns != null) {
116
+ turnsReportedRuns += 1;
117
+ totalTurns += r.num_turns;
118
+ }
119
+ else {
120
+ turnsUnavailableRuns += 1;
121
+ }
95
122
  activeDurationMs += r.duration_ms ?? 0;
96
123
  }
97
- return { totalCostUsd, estimatedCostUsd, totalTokens, totalTurns, activeDurationMs, bySurface, totalRuns: rows.length };
124
+ for (const bucket of Object.values(bySurface)) {
125
+ if ((bucket.unpricedCount ?? 0) === 0)
126
+ delete bucket.unpricedCount;
127
+ }
128
+ return {
129
+ totalCostUsd,
130
+ estimatedCostUsd,
131
+ totalTokens: usageReportedRuns > 0 ? totalTokens : rows.length > 0 ? null : 0,
132
+ totalTurns: turnsReportedRuns > 0 ? totalTurns : rows.length > 0 ? null : 0,
133
+ pricedRuns,
134
+ unpricedRuns,
135
+ usageReportedRuns,
136
+ usageUnavailableRuns,
137
+ turnsReportedRuns,
138
+ turnsUnavailableRuns,
139
+ activeDurationMs,
140
+ bySurface,
141
+ totalRuns: rows.length,
142
+ };
98
143
  }
99
144
  /**
100
145
  * Per-provider aggregation for the Analytics dashboard. Returns counts +
@@ -78,6 +78,37 @@ function sanitizeFilename(name) {
78
78
  function escapeUserAttachmentTag(s) {
79
79
  return s.replace(/<\/user-attachment>/gi, '<\\/user-attachment>');
80
80
  }
81
+ /**
82
+ * Resolve a sidecar-controlled stored filename without ever allowing it to
83
+ * escape the app-managed attachment directory. Sidecars live on disk and can
84
+ * be edited by the local user (or left behind by an older version), so treating
85
+ * `storedName` as trusted would let an attachment prompt expose an arbitrary
86
+ * absolute path to the provider. The realpath check also rejects symlink
87
+ * escapes, which a lexical `path.resolve` containment check alone misses.
88
+ */
89
+ function resolveManagedAttachmentPath(dir, storedName) {
90
+ if (typeof storedName !== 'string' ||
91
+ storedName.length === 0 ||
92
+ storedName !== path_1.default.basename(storedName) ||
93
+ storedName.includes('/') ||
94
+ storedName.includes('\\')) {
95
+ throw new Error(`Unsafe attachment stored name: ${JSON.stringify(storedName)}`);
96
+ }
97
+ const candidate = path_1.default.resolve(dir, storedName);
98
+ const lexicalRelative = path_1.default.relative(path_1.default.resolve(dir), candidate);
99
+ if (lexicalRelative === '..' || lexicalRelative.startsWith(`..${path_1.default.sep}`) || path_1.default.isAbsolute(lexicalRelative)) {
100
+ throw new Error(`Unsafe attachment path: ${JSON.stringify(storedName)}`);
101
+ }
102
+ if (!fs_1.default.existsSync(candidate))
103
+ return null;
104
+ const realDir = fs_1.default.realpathSync(dir);
105
+ const realCandidate = fs_1.default.realpathSync(candidate);
106
+ const realRelative = path_1.default.relative(realDir, realCandidate);
107
+ if (realRelative === '..' || realRelative.startsWith(`..${path_1.default.sep}`) || path_1.default.isAbsolute(realRelative)) {
108
+ throw new Error(`Unsafe attachment symlink: ${JSON.stringify(storedName)}`);
109
+ }
110
+ return realCandidate;
111
+ }
81
112
  class AttachmentManager {
82
113
  homeDir;
83
114
  constructor(homeDir = os_1.default.homedir()) {
@@ -180,8 +211,7 @@ class AttachmentManager {
180
211
  const meta = this.readMeta(slug, ticketKey, attachmentId);
181
212
  if (!meta)
182
213
  return null;
183
- const abs = path_1.default.join(this.ticketDir(slug, ticketKey), meta.storedName);
184
- return fs_1.default.existsSync(abs) ? abs : null;
214
+ return resolveManagedAttachmentPath(this.ticketDir(slug, ticketKey), meta.storedName);
185
215
  }
186
216
  getMeta(slug, ticketKey, attachmentId) {
187
217
  return this.readMeta(slug, ticketKey, attachmentId);
@@ -191,8 +221,11 @@ class AttachmentManager {
191
221
  if (!meta)
192
222
  return false;
193
223
  const dir = this.ticketDir(opts.slug, opts.ticketKey);
194
- const bin = path_1.default.join(dir, meta.storedName);
195
- if (fs_1.default.existsSync(bin))
224
+ // Validate the sidecar-controlled path before deleting either the sidecar
225
+ // or ticket-store reference. On rejection the operation is atomic from the
226
+ // caller's perspective and an out-of-root target is never touched.
227
+ const bin = resolveManagedAttachmentPath(dir, meta.storedName);
228
+ if (bin)
196
229
  fs_1.default.unlinkSync(bin);
197
230
  const side = this.sidecarPath(opts.slug, opts.ticketKey, opts.attachmentId);
198
231
  if (fs_1.default.existsSync(side))
@@ -246,14 +279,16 @@ class AttachmentManager {
246
279
  */
247
280
  async getClaudeArgs(slug, ticketKey, attachmentIds) {
248
281
  const textBlocks = [];
282
+ const imagePaths = [];
249
283
  for (const id of attachmentIds) {
250
284
  const meta = this.readMeta(slug, ticketKey, id);
251
285
  if (!meta)
252
286
  continue;
253
- const abs = path_1.default.join(this.ticketDir(slug, ticketKey), meta.storedName);
254
- if (!fs_1.default.existsSync(abs))
287
+ const abs = resolveManagedAttachmentPath(this.ticketDir(slug, ticketKey), meta.storedName);
288
+ if (!abs)
255
289
  continue;
256
290
  if (meta.mimeType.startsWith(IMAGE_MIME_PREFIX)) {
291
+ imagePaths.push(abs);
257
292
  textBlocks.push(wrapUserAttachment(meta, `@${abs}`));
258
293
  continue;
259
294
  }
@@ -265,7 +300,7 @@ class AttachmentManager {
265
300
  textBlocks.push(wrapUserAttachment(meta, '[extraction failed]'));
266
301
  }
267
302
  }
268
- return { imageFlags: [], textBlocks };
303
+ return { imageFlags: [], textBlocks, imagePaths };
269
304
  }
270
305
  /**
271
306
  * Synchronous prompt blocks for long-running implement flows where we need to
@@ -282,8 +317,8 @@ class AttachmentManager {
282
317
  const meta = this.readMeta(slug, ticketKey, id);
283
318
  if (!meta)
284
319
  continue;
285
- const abs = path_1.default.join(this.ticketDir(slug, ticketKey), meta.storedName);
286
- if (!fs_1.default.existsSync(abs))
320
+ const abs = resolveManagedAttachmentPath(this.ticketDir(slug, ticketKey), meta.storedName);
321
+ if (!abs)
287
322
  continue;
288
323
  if (meta.mimeType.startsWith(IMAGE_MIME_PREFIX)) {
289
324
  textBlocks.push(wrapUserAttachment(meta, `@${abs}`));
@@ -385,16 +420,15 @@ class AttachmentManager {
385
420
  const meta = this.readAgentMeta(conversationId, attachmentId);
386
421
  if (!meta)
387
422
  return null;
388
- const abs = path_1.default.join(this.agentDir(conversationId), meta.storedName);
389
- return fs_1.default.existsSync(abs) ? abs : null;
423
+ return resolveManagedAttachmentPath(this.agentDir(conversationId), meta.storedName);
390
424
  }
391
425
  async deleteAgent(conversationId, attachmentId) {
392
426
  const meta = this.readAgentMeta(conversationId, attachmentId);
393
427
  if (!meta)
394
428
  return false;
395
429
  const dir = this.agentDir(conversationId);
396
- const bin = path_1.default.join(dir, meta.storedName);
397
- if (fs_1.default.existsSync(bin))
430
+ const bin = resolveManagedAttachmentPath(dir, meta.storedName);
431
+ if (bin)
398
432
  fs_1.default.unlinkSync(bin);
399
433
  const side = this.agentSidecarPath(conversationId, attachmentId);
400
434
  if (fs_1.default.existsSync(side))
@@ -422,8 +456,8 @@ class AttachmentManager {
422
456
  const meta = this.readAgentMeta(conversationId, id);
423
457
  if (!meta)
424
458
  continue;
425
- const abs = path_1.default.join(this.agentDir(conversationId), meta.storedName);
426
- if (!fs_1.default.existsSync(abs))
459
+ const abs = resolveManagedAttachmentPath(this.agentDir(conversationId), meta.storedName);
460
+ if (!abs)
427
461
  continue;
428
462
  if (meta.mimeType.startsWith(IMAGE_MIME_PREFIX)) {
429
463
  imagePaths.push(abs);
@@ -7,6 +7,7 @@ exports.BlueprintChatManager = void 0;
7
7
  const crypto_1 = require("crypto");
8
8
  const tree_kill_1 = __importDefault(require("tree-kill"));
9
9
  const providers_1 = require("./providers");
10
+ const runtime_1 = require("./providers/runtime");
10
11
  const spawn_lifecycle_1 = require("./spawn-lifecycle");
11
12
  const cli_prompt_1 = require("./util/cli-prompt");
12
13
  const result_event_1 = require("./result-event");
@@ -129,12 +130,17 @@ class BlueprintChatManager {
129
130
  async _runTurn(conversation, userText, options) {
130
131
  const conversationId = conversation.id;
131
132
  const adapter = (0, providers_1.getAdapter)(conversation.provider);
132
- const catalog = new Set(adapter.modelCatalog().map((m) => m.value));
133
+ const toolPolicy = (0, runtime_1.pureOutputToolPolicy)(adapter);
134
+ if (!toolPolicy) {
135
+ throw new Error(`provider_tool_policy_unsupported:${adapter.id}:pure-output`);
136
+ }
133
137
  const requested = options.model || conversation.model;
134
- const model = requested && catalog.has(requested) ? requested : adapter.defaultModel();
138
+ const model = requested && (0, providers_1.isModelAvailableForAdapter)(adapter, requested)
139
+ ? requested
140
+ : adapter.defaultModel();
135
141
  // Reasoning effort: only for providers with the knob, only when the value
136
142
  // is in their catalog (else undefined = provider default).
137
- const efforts = adapter.capabilities.reasoningEfforts ?? [];
143
+ const efforts = (0, providers_1.reasoningEffortsForModel)(adapter, model);
138
144
  const requestedEffort = options.reasoningEffort;
139
145
  const reasoningEffort = requestedEffort
140
146
  && efforts.includes(requestedEffort)
@@ -156,20 +162,29 @@ class BlueprintChatManager {
156
162
  let streamed = '';
157
163
  let capturedSessionId = useResume ? conversation.session_id ?? null : null;
158
164
  let capturedError = null;
165
+ // `chat-turn` / `chat-resume` intentionally ignore `systemPrompt` for
166
+ // providers whose normal project-chat stance lives in their cwd
167
+ // instructions file. The day-0 Builder has no project instructions, so
168
+ // its schema/operator prompt must ride in the user prompt instead.
169
+ const prompt = adapter.capabilities.systemPromptArg
170
+ ? userText
171
+ : `${blueprint_operator_prompt_1.BUILDER_SYSTEM_PROMPT}\n\n---\n\n${userText}`;
159
172
  console.log(`[blueprint-chat] turn start conv=${conversationId} provider=${adapter.id} action=${action} model=${model}`);
173
+ const buildOpts = {
174
+ prompt,
175
+ systemPrompt: adapter.capabilities.systemPromptArg ? blueprint_operator_prompt_1.BUILDER_SYSTEM_PROMPT : undefined,
176
+ model,
177
+ sessionId: useResume ? conversation.session_id ?? undefined : undefined,
178
+ reasoning_effort: reasoningEffort,
179
+ toolPolicy,
180
+ };
160
181
  const invocation = this._awaitWhileLive((0, spawn_lifecycle_1.runAiCliInvocation)({
161
182
  adapter,
162
183
  action,
163
184
  cwd,
164
- env: { ...process.env },
185
+ env: (0, runtime_1.buildProviderEnv)(adapter, buildOpts),
165
186
  spawn: this._spawnOwned.bind(this),
166
- buildOpts: {
167
- prompt: userText,
168
- systemPrompt: adapter.capabilities.systemPromptArg ? blueprint_operator_prompt_1.BUILDER_SYSTEM_PROMPT : undefined,
169
- model,
170
- sessionId: useResume ? conversation.session_id ?? undefined : undefined,
171
- reasoning_effort: reasoningEffort,
172
- },
187
+ buildOpts,
173
188
  onSpawn: (child) => {
174
189
  if (this._disposed) {
175
190
  this._terminate(child);
@@ -265,7 +280,7 @@ class BlueprintChatManager {
265
280
  return;
266
281
  }
267
282
  // Auto-heal a stale session: a resume with no text retries once fresh.
268
- if (canResume && !r.spawnFailed && !r.text) {
283
+ if (canResume && !r.spawnFailed && !r.error && !r.text) {
269
284
  (0, blueprint_store_1.updateBlueprintConversation)(this._db, conversationId, { session_id: null });
270
285
  r = await invoke(false);
271
286
  if (this._disposed || r.disposed)
@@ -286,15 +301,23 @@ class BlueprintChatManager {
286
301
  record(r, 'failed');
287
302
  return;
288
303
  }
304
+ if (r.error || r.code !== 0) {
305
+ persistSession(null);
306
+ const reason = r.error ||
307
+ (r.stderrTail ? r.stderrTail.split('\n').filter(Boolean).slice(-3).join(' ').slice(0, 300) : '') ||
308
+ `${adapter.binary} exited with code ${r.code ?? 'unknown'}`;
309
+ this._emitError(conversationId, reason);
310
+ record(r, 'failed');
311
+ return;
312
+ }
289
313
  if (r.text) {
290
314
  settleText(r);
291
315
  record(r, 'success');
292
316
  return;
293
317
  }
294
318
  persistSession(null);
295
- const reason = r.error ||
296
- (r.stderrTail ? r.stderrTail.split('\n').filter(Boolean).slice(-3).join(' ').slice(0, 300) : '') ||
297
- (r.code !== 0 ? `${adapter.binary} exited with code ${r.code}` : 'The Builder returned no output.');
319
+ const reason = (r.stderrTail ? r.stderrTail.split('\n').filter(Boolean).slice(-3).join(' ').slice(0, 300) : '') ||
320
+ 'The Builder returned no output.';
298
321
  this._emitError(conversationId, reason);
299
322
  record(r, 'failed');
300
323
  }
@@ -302,8 +325,10 @@ class BlueprintChatManager {
302
325
  if (this._disposed)
303
326
  return;
304
327
  try {
328
+ const finishedAt = new Date().toISOString();
305
329
  const { result, estimated } = (0, result_event_1.finaliseInvocationResult)(adapter, outcome.events, {
306
330
  fallbackModel: model,
331
+ durationMs: Math.max(0, Date.parse(finishedAt) - Date.parse(outcome.startedAt)),
307
332
  });
308
333
  (0, desktop_db_1.recordAgentInvocation)(this._db, {
309
334
  id: (0, crypto_1.randomUUID)(),
@@ -312,7 +337,7 @@ class BlueprintChatManager {
312
337
  provider: adapter.id,
313
338
  status,
314
339
  started_at: outcome.startedAt,
315
- finished_at: new Date().toISOString(),
340
+ finished_at: finishedAt,
316
341
  total_cost_usd_estimated: estimated,
317
342
  ...result,
318
343
  });