specrails-desktop 2.58.1 → 2.59.1

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 (438) hide show
  1. package/README.md +2 -5
  2. package/client/dist/assets/{ActivityFeedPage-BQGppIR0.js → ActivityFeedPage-vAY9C71W.js} +1 -1
  3. package/client/dist/assets/AgentBrowserCapture-CpwjGXQm.js +1 -0
  4. package/client/dist/assets/AgentModeAnalyticsPane-BHeoJVxQ.js +2 -0
  5. package/client/dist/assets/AgentModeCodePane-B0Nf3YkN.js +2 -0
  6. package/client/dist/assets/{AgentModeJobsPane-DR_06oqH.js → AgentModeJobsPane-CpbIItY_.js} +1 -1
  7. package/client/dist/assets/{AgentsPage-BKIKjm_1.js → AgentsPage-BVJAFXn3.js} +3 -3
  8. package/client/dist/assets/{AnalyticsPage-069npRVo.js → AnalyticsPage-DDWees-K.js} +1 -1
  9. package/client/dist/assets/CodePage-CzV5tkvS.js +3 -0
  10. package/client/dist/assets/{DesktopAnalyticsPage-qtWAPM47.js → DesktopAnalyticsPage-DfRHNK7i.js} +1 -1
  11. package/client/dist/assets/DocsDialog-BxduBYbh.js +11 -0
  12. package/client/dist/assets/DocsPage-DOVeRJn_.js +11 -0
  13. package/client/dist/assets/{ExportDropdown-BcMpvV89.js → ExportDropdown-BnKVV8UK.js} +1 -1
  14. package/client/dist/assets/InteractiveJobComposer-Da9dvM-0.js +19 -0
  15. package/client/dist/assets/JobDetailModal-DhwKbZGe.js +1 -0
  16. package/client/dist/assets/JobDetailPage-MtPE-E_1.js +1 -0
  17. package/client/dist/assets/JobsPage-0vdlbNik.js +1 -0
  18. package/client/dist/assets/LoopBuilderPage-DCUHozXh.js +1 -0
  19. package/client/dist/assets/{LoopPreviewModal-je5pSCf-.js → LoopPreviewModal-Df-S8NGf.js} +1 -1
  20. package/client/dist/assets/LoopWindowSurface-sdb7QbSo.js +1 -0
  21. package/client/dist/assets/LoopsPage-CDfCC9Px.js +2 -0
  22. package/client/dist/assets/MinimizedChatsContext-DKlPf54Y.js +1 -0
  23. package/client/dist/assets/PluginsPage-iL0G3rQQ.js +2 -0
  24. package/client/dist/assets/ProjectSettingsDialog-DOkjT7xv.js +1 -0
  25. package/client/dist/assets/{RepositoryDeliveries-BiwJuS8r.js → RepositoryDeliveries-CJVpOUpZ.js} +1 -1
  26. package/client/dist/assets/RepositoryScopeSelector-BeM-2gIZ.js +2 -0
  27. package/client/dist/assets/ReviewPacketPage-EBCim2w_.js +4 -0
  28. package/client/dist/assets/TemplatePreviewModal-1-92UGwN.js +1 -0
  29. package/client/dist/assets/TicketDetailModalContext-CkVXuHxU.js +16 -0
  30. package/client/dist/assets/{Trans-D3AfDylf.js → Trans-CzhPjnjB.js} +1 -1
  31. package/client/dist/assets/agent--8QAfoXL.js +1 -0
  32. package/client/dist/assets/agent-3HxXkIWX.js +1 -0
  33. package/client/dist/assets/agent-BjttZLIl.js +1 -0
  34. package/client/dist/assets/agent-CpFCz32T.js +1 -0
  35. package/client/dist/assets/agent-DVnw6ybO.js +1 -0
  36. package/client/dist/assets/agent-Z5s_GTP0.js +1 -0
  37. package/client/dist/assets/agent-hv57-zyG.js +1 -0
  38. package/client/dist/assets/agent-xGrZ_aN1.js +1 -0
  39. package/client/dist/assets/{agentRuntime-DGz_IqKW.js → agentRuntime-15Pkk6we.js} +1 -1
  40. package/client/dist/assets/agentRuntime-Bpx3yWHK.js +1 -0
  41. package/client/dist/assets/{agentRuntime-Bg9tbmqm.js → agentRuntime-C78n13To.js} +1 -1
  42. package/client/dist/assets/{agentRuntime-D-GJVGkj.js → agentRuntime-CCI7XF4c.js} +1 -1
  43. package/client/dist/assets/{agentRuntime-D2__CVI_.js → agentRuntime-CKDHe2-a.js} +1 -1
  44. package/client/dist/assets/{agentRuntime-D35dGVOP.js → agentRuntime-Ddqvp2bP.js} +1 -1
  45. package/client/dist/assets/{agentRuntime-hInH9Fzj.js → agentRuntime-TXPQowKJ.js} +1 -1
  46. package/client/dist/assets/{agentRuntime-O1liDt6G.js → agentRuntime-hqZRy08E.js} +1 -1
  47. package/client/dist/assets/{builder-DGETbtZj.js → builder-B9gorTnT.js} +1 -1
  48. package/client/dist/assets/{builder-DyQperXU.js → builder-Bvw5s3Mi.js} +1 -1
  49. package/client/dist/assets/{builder-C1neIYGR.js → builder-CL5l4dU3.js} +1 -1
  50. package/client/dist/assets/{builder-DXEJUlC0.js → builder-CVDTVrQX.js} +1 -1
  51. package/client/dist/assets/{builder-CzW5ovyI.js → builder-CpS9liE9.js} +1 -1
  52. package/client/dist/assets/{builder-CvnFm0im.js → builder-DNctIL9Q.js} +1 -1
  53. package/client/dist/assets/{builder-D06L7iPy.js → builder-De2NxX6o.js} +1 -1
  54. package/client/dist/assets/{builder-C0FyvM83.js → builder-DeYT7Y-m.js} +1 -1
  55. package/client/dist/assets/{button-Ccc9Dc-D.js → button-D1xExSTF.js} +1 -1
  56. package/client/dist/assets/{common-DmsclwjP.js → common-Bdi0qN4S.js} +1 -1
  57. package/client/dist/assets/{common-C4Yvvhp6.js → common-Bhb9zN1P.js} +1 -1
  58. package/client/dist/assets/{common-od8WThlf.js → common-BykKlZg1.js} +1 -1
  59. package/client/dist/assets/{common-C0BOdeNi.js → common-CvnNNSJA.js} +1 -1
  60. package/client/dist/assets/{common-BCK7c9Pv.js → common-D5zk_mqq.js} +1 -1
  61. package/client/dist/assets/{common-DpLWwgI1.js → common-DQNO_al5.js} +1 -1
  62. package/client/dist/assets/{common-OCvl3c8t.js → common-Dax30Vmp.js} +1 -1
  63. package/client/dist/assets/{common-c0aujEZC.js → common-DkllpVSb.js} +1 -1
  64. package/client/dist/assets/{dialog-xXB2q5HJ.js → dialog-CMgps2MY.js} +1 -1
  65. package/client/dist/assets/external-link-D6XxC_Cu.js +1 -0
  66. package/client/dist/assets/file-text-BcLCfcWZ.js +1 -0
  67. package/client/dist/assets/{formatDistanceToNow-aBss1Nai.js → formatDistanceToNow-DlQaXc7c.js} +1 -1
  68. package/client/dist/assets/{getTimezoneOffsetInMilliseconds-DOGj1Mfz.js → getTimezoneOffsetInMilliseconds-DxUFGcVq.js} +1 -1
  69. package/client/dist/assets/index-Bfunvi6w.css +2 -0
  70. package/client/dist/assets/index-Czov72g7.js +83 -0
  71. package/client/dist/assets/integrations-BkpRUbvp.js +1 -0
  72. package/client/dist/assets/integrations-Bo8qbJf4.js +1 -0
  73. package/client/dist/assets/integrations-CmSrKzZb.js +1 -0
  74. package/client/dist/assets/integrations-DGtq26Rr.js +1 -0
  75. package/client/dist/assets/integrations-De38GFnQ.js +1 -0
  76. package/client/dist/assets/integrations-Dw6zdtb-.js +1 -0
  77. package/client/dist/assets/integrations-Dwl9KQp0.js +1 -0
  78. package/client/dist/assets/integrations-Refg_d2I.js +1 -0
  79. package/client/dist/assets/jira-api-CbJvbRYx.js +1 -0
  80. package/client/dist/assets/{jobs-KG4Sn12X.js → jobs-1G3wodhl.js} +1 -1
  81. package/client/dist/assets/{jobs-6_-r75yC.js → jobs-B4pu8TdF.js} +1 -1
  82. package/client/dist/assets/{jobs-pPv4KWjb.js → jobs-BD47Ov11.js} +1 -1
  83. package/client/dist/assets/{jobs-B1yqyId5.js → jobs-BLOoVRRl.js} +1 -1
  84. package/client/dist/assets/{jobs-BKiK-ZWh.js → jobs-CSrRx5lD.js} +1 -1
  85. package/client/dist/assets/{jobs-BFuV7ttH.js → jobs-DZAbh1m2.js} +1 -1
  86. package/client/dist/assets/{jobs-DmKV7b7d.js → jobs-epP7gt8W.js} +1 -1
  87. package/client/dist/assets/{jobs-BKa8LuwA.js → jobs-tWjcDF_6.js} +1 -1
  88. package/client/dist/assets/loops--KWr0X-w.js +1 -0
  89. package/client/dist/assets/{loops-BO3V8244.js → loops-3IutojWt.js} +1 -1
  90. package/client/dist/assets/{loops-C-hZK8pf.js → loops-BAosWtXE.js} +1 -1
  91. package/client/dist/assets/{loops-eUnCfLjC.js → loops-BISDssL5.js} +1 -1
  92. package/client/dist/assets/{loops-Dv849ckF.js → loops-Cm55CXJ4.js} +1 -1
  93. package/client/dist/assets/{loops-CDOWZ0ci.js → loops-D7tL_Saw.js} +1 -1
  94. package/client/dist/assets/{loops-BJDY9FTh.js → loops-DHQtnfze.js} +1 -1
  95. package/client/dist/assets/{loops-Q9LIgGkq.js → loops-kIJJkaxf.js} +1 -1
  96. package/client/dist/assets/nav--u7iPYlS.js +1 -0
  97. package/client/dist/assets/nav-C5Jgfaby.js +1 -0
  98. package/client/dist/assets/nav-CAna_Wpt.js +1 -0
  99. package/client/dist/assets/nav-CCLqeyHo.js +1 -0
  100. package/client/dist/assets/nav-Cf3c90Jk.js +1 -0
  101. package/client/dist/assets/nav-DMYQaQjR.js +1 -0
  102. package/client/dist/assets/nav-Df_PkZ9F.js +1 -0
  103. package/client/dist/assets/nav-VyNGBJwj.js +1 -0
  104. package/client/dist/assets/{project-repositories-CSnfzUpg.js → project-repositories-BkBD3rxx.js} +1 -1
  105. package/client/dist/assets/provider-capabilities-lePfeT_Y.js +1 -0
  106. package/client/dist/assets/save-OsUqVn99.js +1 -0
  107. package/client/dist/assets/settings-B5Hy_Kol.js +1 -0
  108. package/client/dist/assets/settings-BBj_kZaD.js +1 -0
  109. package/client/dist/assets/settings-ByVE7jdZ.js +1 -0
  110. package/client/dist/assets/settings-COlH_P33.js +1 -0
  111. package/client/dist/assets/settings-D77mC_Lb.js +1 -0
  112. package/client/dist/assets/settings-DZKy7MqN.js +1 -0
  113. package/client/dist/assets/settings-Dbyd6gmj.js +1 -0
  114. package/client/dist/assets/settings-Dn-pXukr.js +1 -0
  115. package/client/dist/assets/setup-Be485n2u.js +1 -0
  116. package/client/dist/assets/setup-CKGUxKr-.js +1 -0
  117. package/client/dist/assets/setup-Ck1Uwd7_.js +1 -0
  118. package/client/dist/assets/setup-Cm774a6v.js +1 -0
  119. package/client/dist/assets/setup-DIqMQpxt.js +1 -0
  120. package/client/dist/assets/setup-DfCsoNFL.js +1 -0
  121. package/client/dist/assets/setup-DkeE8mP0.js +1 -0
  122. package/client/dist/assets/setup-xVntVaz1.js +1 -0
  123. package/client/dist/assets/{spending-HBSk-6jv.js → spending-CYotH-9N.js} +1 -1
  124. package/client/dist/assets/subscriptionUsage-BH73RO86.js +1 -0
  125. package/client/dist/assets/subscriptionUsage-BWTeWlHx.js +1 -0
  126. package/client/dist/assets/subscriptionUsage-CBh82zVa.js +1 -0
  127. package/client/dist/assets/subscriptionUsage-CHcQ7lLe.js +1 -0
  128. package/client/dist/assets/subscriptionUsage-CdlE5Mg1.js +1 -0
  129. package/client/dist/assets/subscriptionUsage-DUPBmSQi.js +1 -0
  130. package/client/dist/assets/subscriptionUsage-DrlwrJ17.js +1 -0
  131. package/client/dist/assets/subscriptionUsage-EFu0mIK3.js +1 -0
  132. package/client/dist/assets/tauri-shell-DE6LfGPw.js +2 -0
  133. package/client/dist/assets/{useDesktop-0unHDBJJ.js → useDesktop-DpxVGfUC.js} +1 -1
  134. package/client/dist/assets/{useRuntimeRuns-BN1rhyEm.js → useRuntimeRuns-CfGRbhr9.js} +1 -1
  135. package/client/dist/assets/useSharedWebSocket-C17TGqT8.js +2 -0
  136. package/client/dist/index.html +33 -32
  137. package/docs/README.md +1 -9
  138. package/docs/agent-live-steering.md +0 -6
  139. package/docs/agents/README.md +0 -2
  140. package/docs/agents/mcp.md +1 -14
  141. package/docs/cli.md +0 -13
  142. package/docs/codex.md +2 -75
  143. package/docs/creating-specs.md +0 -39
  144. package/docs/customizing.md +1 -15
  145. package/docs/getting-started.md +0 -23
  146. package/docs/guide/de/agents/2-profiles-and-the-balanced-default.md +0 -8
  147. package/docs/guide/de/agents/4-custom-agents-catalog.md +0 -15
  148. package/docs/guide/de/getting-started/1-what-is-specrails.md +0 -2
  149. package/docs/guide/de/getting-started/2-installing-and-first-run.md +0 -2
  150. package/docs/guide/de/getting-started/3-adding-your-first-project.md +0 -4
  151. package/docs/guide/de/getting-started/4-the-dashboard-tour.md +0 -5
  152. package/docs/guide/de/integrations/1-ai-providers.md +2 -50
  153. package/docs/guide/de/integrations/2-plugins.md +0 -8
  154. package/docs/guide/de/integrations/5-mcp-server.md +1 -14
  155. package/docs/guide/de/integrations/6-agent-chat.md +0 -11
  156. package/docs/guide/de/integrations/7-local-engines.md +0 -4
  157. package/docs/guide/de/pipeline/1-rails-and-jobs.md +0 -13
  158. package/docs/guide/de/pipeline/2-the-job-detail-view.md +0 -3
  159. package/docs/guide/de/pipeline/4-picking-an-engine-per-rail.md +0 -20
  160. package/docs/guide/de/pipeline/5-the-loop-builder.md +0 -6
  161. package/docs/guide/de/specs/2-add-spec-quick-mode.md +0 -7
  162. package/docs/guide/en/agents/2-profiles-and-the-balanced-default.md +0 -10
  163. package/docs/guide/en/agents/4-custom-agents-catalog.md +0 -25
  164. package/docs/guide/en/getting-started/1-what-is-specrails.md +0 -2
  165. package/docs/guide/en/getting-started/2-installing-and-first-run.md +0 -2
  166. package/docs/guide/en/getting-started/3-adding-your-first-project.md +0 -4
  167. package/docs/guide/en/getting-started/4-the-dashboard-tour.md +0 -5
  168. package/docs/guide/en/insights/3-code-explorer.md +0 -5
  169. package/docs/guide/en/integrations/1-ai-providers.md +10 -99
  170. package/docs/guide/en/integrations/2-plugins.md +0 -8
  171. package/docs/guide/en/integrations/5-mcp-server.md +1 -1
  172. package/docs/guide/en/integrations/6-agent-chat.md +0 -17
  173. package/docs/guide/en/integrations/7-local-engines.md +0 -4
  174. package/docs/guide/en/pipeline/1-rails-and-jobs.md +2 -24
  175. package/docs/guide/en/pipeline/2-the-job-detail-view.md +0 -3
  176. package/docs/guide/en/pipeline/4-picking-an-engine-per-rail.md +10 -65
  177. package/docs/guide/en/pipeline/5-the-loop-builder.md +0 -7
  178. package/docs/guide/en/specs/2-add-spec-quick-mode.md +0 -11
  179. package/docs/guide/es/agents/2-profiles-and-the-balanced-default.md +0 -8
  180. package/docs/guide/es/agents/4-custom-agents-catalog.md +0 -15
  181. package/docs/guide/es/getting-started/1-what-is-specrails.md +0 -2
  182. package/docs/guide/es/getting-started/2-installing-and-first-run.md +0 -2
  183. package/docs/guide/es/getting-started/3-adding-your-first-project.md +0 -4
  184. package/docs/guide/es/getting-started/4-the-dashboard-tour.md +0 -5
  185. package/docs/guide/es/integrations/1-ai-providers.md +10 -75
  186. package/docs/guide/es/integrations/2-plugins.md +0 -8
  187. package/docs/guide/es/integrations/5-mcp-server.md +1 -1
  188. package/docs/guide/es/integrations/6-agent-chat.md +0 -11
  189. package/docs/guide/es/integrations/7-local-engines.md +0 -4
  190. package/docs/guide/es/pipeline/1-rails-and-jobs.md +3 -13
  191. package/docs/guide/es/pipeline/2-the-job-detail-view.md +0 -3
  192. package/docs/guide/es/pipeline/4-picking-an-engine-per-rail.md +10 -53
  193. package/docs/guide/es/pipeline/5-the-loop-builder.md +1 -6
  194. package/docs/guide/es/specs/2-add-spec-quick-mode.md +0 -7
  195. package/docs/guide/fr/agents/2-profiles-and-the-balanced-default.md +0 -8
  196. package/docs/guide/fr/agents/4-custom-agents-catalog.md +0 -15
  197. package/docs/guide/fr/getting-started/1-what-is-specrails.md +0 -2
  198. package/docs/guide/fr/getting-started/2-installing-and-first-run.md +0 -2
  199. package/docs/guide/fr/getting-started/3-adding-your-first-project.md +0 -4
  200. package/docs/guide/fr/getting-started/4-the-dashboard-tour.md +0 -5
  201. package/docs/guide/fr/integrations/1-ai-providers.md +2 -50
  202. package/docs/guide/fr/integrations/2-plugins.md +0 -8
  203. package/docs/guide/fr/integrations/5-mcp-server.md +1 -14
  204. package/docs/guide/fr/integrations/6-agent-chat.md +0 -11
  205. package/docs/guide/fr/integrations/7-local-engines.md +0 -4
  206. package/docs/guide/fr/pipeline/1-rails-and-jobs.md +0 -13
  207. package/docs/guide/fr/pipeline/2-the-job-detail-view.md +0 -3
  208. package/docs/guide/fr/pipeline/4-picking-an-engine-per-rail.md +0 -22
  209. package/docs/guide/fr/pipeline/5-the-loop-builder.md +0 -6
  210. package/docs/guide/fr/specs/2-add-spec-quick-mode.md +0 -7
  211. package/docs/guide/it/agents/2-profiles-and-the-balanced-default.md +0 -8
  212. package/docs/guide/it/agents/4-custom-agents-catalog.md +0 -15
  213. package/docs/guide/it/getting-started/1-what-is-specrails.md +0 -2
  214. package/docs/guide/it/getting-started/2-installing-and-first-run.md +0 -2
  215. package/docs/guide/it/getting-started/3-adding-your-first-project.md +0 -4
  216. package/docs/guide/it/getting-started/4-the-dashboard-tour.md +0 -5
  217. package/docs/guide/it/integrations/1-ai-providers.md +2 -48
  218. package/docs/guide/it/integrations/2-plugins.md +0 -8
  219. package/docs/guide/it/integrations/5-mcp-server.md +1 -14
  220. package/docs/guide/it/integrations/6-agent-chat.md +0 -11
  221. package/docs/guide/it/integrations/7-local-engines.md +0 -4
  222. package/docs/guide/it/pipeline/1-rails-and-jobs.md +2 -13
  223. package/docs/guide/it/pipeline/2-the-job-detail-view.md +0 -3
  224. package/docs/guide/it/pipeline/4-picking-an-engine-per-rail.md +0 -20
  225. package/docs/guide/it/pipeline/5-the-loop-builder.md +1 -6
  226. package/docs/guide/it/specs/2-add-spec-quick-mode.md +0 -7
  227. package/docs/guide/ja/agents/2-profiles-and-the-balanced-default.md +0 -9
  228. package/docs/guide/ja/agents/4-custom-agents-catalog.md +0 -15
  229. package/docs/guide/ja/getting-started/1-what-is-specrails.md +0 -2
  230. package/docs/guide/ja/getting-started/2-installing-and-first-run.md +0 -2
  231. package/docs/guide/ja/getting-started/3-adding-your-first-project.md +0 -4
  232. package/docs/guide/ja/getting-started/4-the-dashboard-tour.md +0 -5
  233. package/docs/guide/ja/integrations/1-ai-providers.md +1 -69
  234. package/docs/guide/ja/integrations/2-plugins.md +0 -8
  235. package/docs/guide/ja/integrations/5-mcp-server.md +1 -14
  236. package/docs/guide/ja/integrations/6-agent-chat.md +0 -10
  237. package/docs/guide/ja/integrations/7-local-engines.md +0 -4
  238. package/docs/guide/ja/pipeline/1-rails-and-jobs.md +0 -13
  239. package/docs/guide/ja/pipeline/2-the-job-detail-view.md +0 -3
  240. package/docs/guide/ja/pipeline/4-picking-an-engine-per-rail.md +0 -21
  241. package/docs/guide/ja/pipeline/5-the-loop-builder.md +0 -6
  242. package/docs/guide/ja/specs/2-add-spec-quick-mode.md +0 -7
  243. package/docs/guide/pt/agents/2-profiles-and-the-balanced-default.md +0 -8
  244. package/docs/guide/pt/agents/4-custom-agents-catalog.md +0 -15
  245. package/docs/guide/pt/getting-started/1-what-is-specrails.md +0 -2
  246. package/docs/guide/pt/getting-started/2-installing-and-first-run.md +0 -2
  247. package/docs/guide/pt/getting-started/3-adding-your-first-project.md +0 -4
  248. package/docs/guide/pt/getting-started/4-the-dashboard-tour.md +0 -5
  249. package/docs/guide/pt/integrations/1-ai-providers.md +2 -48
  250. package/docs/guide/pt/integrations/2-plugins.md +0 -8
  251. package/docs/guide/pt/integrations/5-mcp-server.md +1 -14
  252. package/docs/guide/pt/integrations/6-agent-chat.md +0 -10
  253. package/docs/guide/pt/integrations/7-local-engines.md +0 -4
  254. package/docs/guide/pt/pipeline/1-rails-and-jobs.md +0 -13
  255. package/docs/guide/pt/pipeline/2-the-job-detail-view.md +0 -2
  256. package/docs/guide/pt/pipeline/4-picking-an-engine-per-rail.md +0 -21
  257. package/docs/guide/pt/pipeline/5-the-loop-builder.md +0 -6
  258. package/docs/guide/pt/specs/2-add-spec-quick-mode.md +0 -7
  259. package/docs/guide/zh/agents/2-profiles-and-the-balanced-default.md +0 -7
  260. package/docs/guide/zh/agents/4-custom-agents-catalog.md +0 -15
  261. package/docs/guide/zh/getting-started/1-what-is-specrails.md +0 -2
  262. package/docs/guide/zh/getting-started/2-installing-and-first-run.md +0 -2
  263. package/docs/guide/zh/getting-started/3-adding-your-first-project.md +0 -4
  264. package/docs/guide/zh/getting-started/4-the-dashboard-tour.md +0 -5
  265. package/docs/guide/zh/integrations/1-ai-providers.md +1 -67
  266. package/docs/guide/zh/integrations/2-plugins.md +0 -8
  267. package/docs/guide/zh/integrations/5-mcp-server.md +1 -14
  268. package/docs/guide/zh/integrations/6-agent-chat.md +0 -9
  269. package/docs/guide/zh/integrations/7-local-engines.md +0 -4
  270. package/docs/guide/zh/pipeline/1-rails-and-jobs.md +0 -13
  271. package/docs/guide/zh/pipeline/2-the-job-detail-view.md +0 -2
  272. package/docs/guide/zh/pipeline/4-picking-an-engine-per-rail.md +0 -20
  273. package/docs/guide/zh/pipeline/5-the-loop-builder.md +0 -6
  274. package/docs/guide/zh/specs/2-add-spec-quick-mode.md +0 -7
  275. package/docs/internals/adding-a-provider.md +7 -0
  276. package/docs/internals/api-reference.md +1 -0
  277. package/docs/internals/core-runtime-updates.md +9 -2
  278. package/docs/internals/desktop-owned-workflows.md +144 -0
  279. package/docs/internals/mission-rail-cards.md +15 -0
  280. package/docs/internals/operations-runbook.md +26 -0
  281. package/docs/internals/programmatic-agent-runtime.md +34 -28
  282. package/docs/internals/project-builder.md +34 -11
  283. package/docs/internals/safe-pr-review-flow.md +25 -7
  284. package/docs/internals/source-map.md +53 -3
  285. package/docs/internals/spec-addenda.md +11 -5
  286. package/docs/internals/visual-design-audit.md +5 -0
  287. package/docs/local-providers.md +8 -13
  288. package/docs/mcp.md +1 -17
  289. package/docs/platforms/macos.md +0 -40
  290. package/docs/platforms/windows-parity.md +1 -1
  291. package/docs/platforms/windows.md +0 -8
  292. package/docs/running-pipelines.md +1 -46
  293. package/docs/subscription-usage.md +19 -0
  294. package/docs/terminal.md +0 -2
  295. package/docs/tracking-cost.md +2 -33
  296. package/package.json +1 -1
  297. package/server/dist/core-execution.js +24 -15
  298. package/server/dist/core-update-manager.js +1 -1
  299. package/server/dist/db/migrations.js +9 -0
  300. package/server/dist/desktop-db.js +23 -10
  301. package/server/dist/desktop-router.js +8 -4
  302. package/server/dist/index.js +10 -5
  303. package/server/dist/mcp/guide.js +1 -0
  304. package/server/dist/mcp/tools/jobs.js +1 -1
  305. package/server/dist/mcp/tools/loops.js +2 -0
  306. package/server/dist/mcp/tools/meta.js +6 -7
  307. package/server/dist/mcp/tools/projects.js +31 -6
  308. package/server/dist/mcp/tools/rails.js +6 -3
  309. package/server/dist/mcp/tools/setup.js +10 -0
  310. package/server/dist/mcp/tools/specs.js +1 -1
  311. package/server/dist/modules/accounting/runtime/pricing.js +6 -1
  312. package/server/dist/modules/agent-runtime/runtime/agent-runtime-bridge.js +169 -17
  313. package/server/dist/modules/agent-runtime/runtime/agent-runtime-metrics.js +1 -1
  314. package/server/dist/modules/agent-runtime/runtime/agent-runtime-settings-router.js +1 -1
  315. package/server/dist/modules/agent-runtime/runtime/agent-runtime-settings.js +42 -0
  316. package/server/dist/modules/agent-runtime/runtime/agent-runtime-verification-suggestions.js +10 -4
  317. package/server/dist/modules/builder/runtime/milestone-chain.js +76 -10
  318. package/server/dist/modules/delivery/runtime/delivery-evidence.js +3 -2
  319. package/server/dist/modules/delivery/runtime/multi-repo-execution.js +10 -5
  320. package/server/dist/modules/delivery/runtime/rail-isolated-launch.js +66 -30
  321. package/server/dist/modules/delivery/runtime/rail-launch-parser.js +8 -0
  322. package/server/dist/modules/delivery/runtime/rail-pr-store.js +3 -3
  323. package/server/dist/modules/delivery/runtime/rail-relaunch.js +154 -0
  324. package/server/dist/modules/delivery/runtime/rails-router.js +157 -36
  325. package/server/dist/modules/delivery/runtime/review-packet.js +1 -1
  326. package/server/dist/modules/execution/runtime/revision-seed.js +6 -2
  327. package/server/dist/modules/loops/runtime/loop-agent-defaults.json +6 -0
  328. package/server/dist/modules/loops/runtime/loop-agents.js +21 -0
  329. package/server/dist/modules/loops/runtime/loop-command-catalog.js +1 -1
  330. package/server/dist/modules/loops/runtime/loop-core-factory.js +19 -3
  331. package/server/dist/modules/loops/runtime/loop-definition-events.js +1 -1
  332. package/server/dist/modules/loops/runtime/loop-definition.js +79 -12
  333. package/server/dist/modules/loops/runtime/loop-effect.js +1 -1
  334. package/server/dist/modules/loops/runtime/loop-executors.js +24 -9
  335. package/server/dist/modules/loops/runtime/loop-factory.js +1 -1
  336. package/server/dist/modules/loops/runtime/loop-graph.js +5 -14
  337. package/server/dist/modules/loops/runtime/loop-implement-recipe.js +53 -0
  338. package/server/dist/modules/loops/runtime/loop-run-manager.js +30 -20
  339. package/server/dist/modules/loops/runtime/loop-templates.js +29 -5
  340. package/server/dist/modules/loops/runtime/loops-router.js +17 -0
  341. package/server/dist/modules/missions/runtime/agent-context-resolver.js +1 -1
  342. package/server/dist/modules/missions/runtime/agent-operator-prompt.js +17 -8
  343. package/server/dist/modules/specs/runtime/spec-models.js +4 -7
  344. package/server/dist/modules/subscription-usage/adapters/claude.js +123 -0
  345. package/server/dist/modules/subscription-usage/adapters/codex.js +164 -0
  346. package/server/dist/modules/subscription-usage/adapters/errors.js +15 -0
  347. package/server/dist/modules/subscription-usage/adapters/http.js +20 -0
  348. package/server/dist/modules/subscription-usage/adapters/local-auth.js +61 -0
  349. package/server/dist/modules/subscription-usage/application.js +12 -0
  350. package/server/dist/modules/subscription-usage/domain.js +104 -0
  351. package/server/dist/modules/subscription-usage/index.js +2 -0
  352. package/server/dist/modules/subscription-usage/ports.js +2 -0
  353. package/server/dist/modules/subscription-usage/runtime/composition.js +20 -0
  354. package/server/dist/modules/subscription-usage/runtime/usage-service.js +111 -0
  355. package/server/dist/project-registry.js +16 -1
  356. package/server/dist/project-repositories.js +51 -2
  357. package/server/dist/project-router-helpers.js +1 -1
  358. package/server/dist/project-router-loop-runs.js +4 -1
  359. package/server/dist/providers/claude-adapter.js +6 -4
  360. package/server/dist/providers/codex-adapter.js +4 -5
  361. package/server/dist/schemas/workflow-definition.schema.json +2 -0
  362. package/server/dist/worktree-manager.js +114 -6
  363. package/server/dist/worktree-overlay.js +4 -4
  364. package/client/dist/assets/AgentBrowserCapture-PI-cxyxi.js +0 -1
  365. package/client/dist/assets/AgentModeAnalyticsPane-mQsOqdXx.js +0 -2
  366. package/client/dist/assets/AgentModeCodePane-BL-vpmlY.js +0 -2
  367. package/client/dist/assets/CodePage-Alitorrv.js +0 -3
  368. package/client/dist/assets/DocsDialog-DwL6rgcT.js +0 -11
  369. package/client/dist/assets/DocsPage-BA1hiJZ-.js +0 -11
  370. package/client/dist/assets/InteractiveJobComposer-B3H1YHr6.js +0 -19
  371. package/client/dist/assets/JobDetailModal-XZQ9eAAy.js +0 -1
  372. package/client/dist/assets/JobDetailPage-B-SIaq_q.js +0 -1
  373. package/client/dist/assets/JobsPage-iQXAv2mx.js +0 -1
  374. package/client/dist/assets/LoopBuilderPage-B_gbFAHn.js +0 -1
  375. package/client/dist/assets/LoopsPage-BZwcY5cu.js +0 -2
  376. package/client/dist/assets/MinimizedChatsContext-CNx_0bZu.js +0 -1
  377. package/client/dist/assets/PluginsPage-DFocnoHX.js +0 -2
  378. package/client/dist/assets/ProjectSettingsDialog-Bh3S6EBB.js +0 -1
  379. package/client/dist/assets/RepositoryScopeSelector-C2mvWBQ6.js +0 -1
  380. package/client/dist/assets/ReviewPacketPage-CrAf-WDy.js +0 -4
  381. package/client/dist/assets/TemplatePreviewModal-C7DordpN.js +0 -1
  382. package/client/dist/assets/TicketDetailModalContext-B-DySpKL.js +0 -16
  383. package/client/dist/assets/agent-BaG10AN6.js +0 -1
  384. package/client/dist/assets/agent-CH4aJ884.js +0 -1
  385. package/client/dist/assets/agent-CLc9fA7v.js +0 -1
  386. package/client/dist/assets/agent-DHh9-AdL.js +0 -1
  387. package/client/dist/assets/agent-DkKnd6Wv.js +0 -1
  388. package/client/dist/assets/agent-aiVhqFW1.js +0 -1
  389. package/client/dist/assets/agent-dbznD3eO.js +0 -1
  390. package/client/dist/assets/agent-sMVFcPwL.js +0 -1
  391. package/client/dist/assets/agentRuntime-BeUb3g9g.js +0 -1
  392. package/client/dist/assets/file-text-CFT-xXKN.js +0 -1
  393. package/client/dist/assets/index-BS7KCWkV.css +0 -2
  394. package/client/dist/assets/index-C4Mv6tV6.js +0 -80
  395. package/client/dist/assets/integrations-CH0zy1Mw.js +0 -1
  396. package/client/dist/assets/integrations-CIAjhd7L.js +0 -1
  397. package/client/dist/assets/integrations-CZdwOe8A.js +0 -1
  398. package/client/dist/assets/integrations-Ct7omMld.js +0 -1
  399. package/client/dist/assets/integrations-D6MSxy3-.js +0 -1
  400. package/client/dist/assets/integrations-DbZa-Zyc.js +0 -1
  401. package/client/dist/assets/integrations-b8pH-vfl.js +0 -1
  402. package/client/dist/assets/integrations-mB-7zoyT.js +0 -1
  403. package/client/dist/assets/jira-api-BYbEPlkr.js +0 -1
  404. package/client/dist/assets/layers-eDMjG41I.js +0 -1
  405. package/client/dist/assets/loops-B4_D-nbg.js +0 -1
  406. package/client/dist/assets/nav-B-tpjyc2.js +0 -1
  407. package/client/dist/assets/nav-BJNXGg29.js +0 -1
  408. package/client/dist/assets/nav-BdhtRLP7.js +0 -1
  409. package/client/dist/assets/nav-ByYgyN5T.js +0 -1
  410. package/client/dist/assets/nav-DA0Mva2c.js +0 -1
  411. package/client/dist/assets/nav-DcJm_Jg_.js +0 -1
  412. package/client/dist/assets/nav-DhMPtAXc.js +0 -1
  413. package/client/dist/assets/nav-DhPb-Isc.js +0 -1
  414. package/client/dist/assets/provider-capabilities-Vi1ZGvAK.js +0 -1
  415. package/client/dist/assets/save-B6wGWwBR.js +0 -1
  416. package/client/dist/assets/settings-3o4Ntzi5.js +0 -1
  417. package/client/dist/assets/settings-B97jlkwD.js +0 -1
  418. package/client/dist/assets/settings-BTaT_bsS.js +0 -1
  419. package/client/dist/assets/settings-CT8w7WiC.js +0 -1
  420. package/client/dist/assets/settings-Cvr8_tvh.js +0 -1
  421. package/client/dist/assets/settings-Dn1qgAC8.js +0 -1
  422. package/client/dist/assets/settings-EShDdg4J.js +0 -1
  423. package/client/dist/assets/settings-tfLhQZRN.js +0 -1
  424. package/client/dist/assets/setup-CBMu3tqH.js +0 -1
  425. package/client/dist/assets/setup-CPh-e6J2.js +0 -1
  426. package/client/dist/assets/setup-CRKLp_ke.js +0 -1
  427. package/client/dist/assets/setup-Cy302v4J.js +0 -1
  428. package/client/dist/assets/setup-D681vTPf.js +0 -1
  429. package/client/dist/assets/setup-DYK0VBkE.js +0 -1
  430. package/client/dist/assets/setup-KAuiIiVs.js +0 -1
  431. package/client/dist/assets/setup-OGX-oCfI.js +0 -1
  432. package/client/dist/assets/useSharedWebSocket-BZMrX4jr.js +0 -2
  433. package/client/dist/assets/wrench-BfmNO9Vw.js +0 -1
  434. package/docs/agy-cli-provider-study.md +0 -257
  435. package/docs/gemini-cli-provider-study.md +0 -301
  436. package/docs/gemini-core-support-evaluation.md +0 -160
  437. package/docs/gemini.md +0 -257
  438. package/docs/kimi.md +0 -291
@@ -1,7 +1,5 @@
1
1
  # Tracking cost
2
2
 
3
- specrails-desktop records AI CLI invocations across Claude, Codex, Gemini, and Kimi and surfaces the available metrics on one Analytics page per project. This guide walks through what's tracked, what's unavailable, and how to read the dashboard.
4
-
5
3
  ## What gets tracked
6
4
 
7
5
  Six surfaces, all per-project:
@@ -15,18 +13,6 @@ Six surfaces, all per-project:
15
13
  | **`smash`** | SMASH runs that break an epic spec into sub-specs |
16
14
  | **`file-summary`** | Code-explorer AI summaries of individual files |
17
15
 
18
- Each invocation row carries the provider, model, status, timestamps, duration,
19
- and relevant ticket/conversation IDs. Token, USD-cost, and turn fields are
20
- nullable because not every CLI reports them; Kimi leaves those values
21
- unavailable rather than writing zero.
22
-
23
- The surface list is app-wide, not a promise that every provider can spawn
24
- every row type. Kimi records its agentic chats/Explore/rails/loops, but Quick
25
- Spec, AI Edit, Contract Refine, SMASH/Re-SMASH, file summaries, construction
26
- story, AI auto-title, Project Builder blueprint/milestone generation, Loop
27
- Decider, and Agent Studio automation fail before spawn. Those rejected
28
- requests create no invocation row; AI auto-title uses a deterministic fallback.
29
-
30
16
  > Cost averages exclude `failed`/`aborted` rows, but those rows still count toward the total run count and the failure rate.
31
17
 
32
18
  ## Authoritative vs estimated cost
@@ -34,24 +20,16 @@ requests create no invocation row; AI auto-title uses a deterministic fallback.
34
20
  Whether a cost figure is exact depends on the provider's CLI, not on which provider you picked:
35
21
 
36
22
  - **Claude cost is provider-billed and authoritative** — the figure comes straight from the CLI's own usage report.
37
- - **Codex and Gemini do not report cost natively**, so the app **estimates** their cost from a local rate-card (`server/modules/accounting/runtime/pricing.ts`) using the captured token counts. Estimated rows are flagged: they render with a `~` tilde in the raw table (hover for the tooltip — *"Estimated from local pricing table — this provider does not report cost natively"*) and feed an "includes ~$X estimated" footnote in the Hero.
38
- - **Kimi reports neither token counts nor a native USD-cost envelope** in its
39
- stream. Specrails does not invent an estimate: cost and token cells stay
40
- unavailable.
23
+ - **Codex do not report cost natively**, so the app **estimates** their cost from a local rate-card (`server/modules/accounting/runtime/pricing.ts`) using the captured token counts. Estimated rows are flagged: they render with a `~` tilde in the raw table (hover for the tooltip — *"Estimated from local pricing table — this provider does not report cost natively"*) and feed an "includes ~$X estimated" footnote in the Hero.
41
24
 
42
25
  On multi-provider projects, a **Provider breakdown** card splits spend across the project's installed engines so you can see authoritative vs estimated at a glance.
43
26
 
44
- > **Fail-soft on unknown models.** The rate card lists specific models (the current Codex and Gemini catalogs). If an invocation uses a model that isn't in the table, the app **does not fabricate a number** — it stores no cost (`total_cost_usd` is left empty), so that row simply shows a blank cost rather than a misleading estimate.
27
+ > **Fail-soft on unknown models.** The rate card lists specific models (the current Codex catalogs). If an invocation uses a model that isn't in the table, the app **does not fabricate a number** — it stores no cost (`total_cost_usd` is left empty), so that row simply shows a blank cost rather than a misleading estimate.
45
28
 
46
29
  ## What's NOT tracked (intentionally)
47
30
 
48
- - **Sidebar chat** — the general-purpose chat panel in the right sidebar. It spawns an AI process, including `kimi -p` when Kimi is selected, but isn't pipeline work, so the app excludes it from analytics by design.
49
31
  - **Setup wizard** — the install/enrich flow when you add a project. It *does* spawn an AI CLI (a genuine model invocation), but it's an interactive one-time wizard rather than a repeatable pipeline job, so it's deliberately left uninstrumented.
50
32
 
51
- If you want the absolute total of what an engine has cost you, your provider's
52
- own console is the source of truth. This is especially important for Kimi,
53
- whose CLI stream provides no billable usage envelope to Desktop.
54
-
55
33
  ## The Analytics page
56
34
 
57
35
  Open **Analytics** from the project right sidebar.
@@ -75,13 +53,6 @@ The big number at the top is total spend in the selected period, with:
75
53
 
76
54
  When the project has zero invocations in the period (e.g. you just started), the Hero shows "Tracking started YYYY-MM-DD" — the date of the first invocation ever recorded. There's no historical backfill; tracking begins at that first row.
77
55
 
78
- ### Provider breakdown
79
-
80
- On multi-provider projects, a dedicated card splits available spend across the
81
- project's installed engines. It is data-driven, so Kimi appears even though its
82
- cost is unavailable. Estimated Codex/Gemini totals carry a `~` tilde. The card
83
- is hidden on single-provider projects.
84
-
85
56
  ### Daily stacked timeline
86
57
 
87
58
  A daily bar chart for the period, stacked by surface. It plots five series — **Jobs, Explore, Quick, Refine, and File summaries**. SMASH spend is still tracked and shows up elsewhere (the Hero per-surface bar, Top models, and the raw table), but it is not currently a separate bar in this chart. Days with zero activity are zero-filled so the x-axis stays regular.
@@ -176,8 +147,6 @@ For the cross-project view, open **Analytics** from the Arc sidebar on the left.
176
147
  - [Customising the app](customizing.md) — set the budget, configure notifications.
177
148
  - [Creating specs](creating-specs.md) — every spec you create adds rows to your analytics.
178
149
  - [Using Codex](codex.md) — how cost works when you run a project on Codex (estimated, ~ tilde).
179
- - [Using Gemini](gemini.md) — how cost works when you run a project on Gemini (also estimated, ~ tilde).
180
- - [Using Kimi](kimi.md) — why Kimi duration/outcome are recorded while tokens and cost remain unavailable.
181
150
 
182
151
  ## Legacy workflow usage
183
152
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "specrails-desktop",
3
- "version": "2.58.1",
3
+ "version": "2.59.1",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -51,16 +51,15 @@ function checkoutSubdirectory(directory) {
51
51
  function registeredScope(checkout, sourcePath) {
52
52
  if (!sourcePath)
53
53
  return undefined;
54
- const prefix = checkoutSubdirectory(sourcePath);
55
- // Core already receives the registered directory itself when it is not a checkout root.
56
- if (!prefix || checkoutSubdirectory(checkout) !== '')
54
+ (0, node_fs_1.realpathSync)(sourcePath);
55
+ const local = (0, node_path_1.relative)(checkout, sourcePath).split(node_path_1.sep).join('/');
56
+ const contained = local && local !== '..' && !local.startsWith('../') && !(0, node_path_1.isAbsolute)(local);
57
+ const prefix = checkoutSubdirectory(checkout) ? (contained ? local : '') : checkoutSubdirectory(sourcePath) || (contained ? local : '');
58
+ if (!prefix)
57
59
  return undefined;
58
- try {
59
- return (0, node_fs_1.statSync)((0, node_path_1.join)(checkout, ...prefix.split('/'))).isDirectory() ? [prefix] : undefined;
60
- }
61
- catch {
62
- return undefined;
63
- }
60
+ if (!(0, node_fs_1.statSync)((0, node_path_1.join)(checkout, ...prefix.split('/'))).isDirectory())
61
+ throw new Error('Selected code workspace is unavailable in the execution checkout');
62
+ return [prefix];
64
63
  }
65
64
  function withoutScope(context) {
66
65
  return { ...context, repositories: context.repositories.map(({ scope: _scope, ...repository }) => repository) };
@@ -74,13 +73,17 @@ function prepareCoreExecution(input) {
74
73
  throw new Error('Invalid Core execution run id');
75
74
  const cwd = (0, node_fs_1.realpathSync)(input.cwd);
76
75
  (0, core_update_state_1.assertWorkspaceCoreReady)(cwd);
77
- const scoped = (repository, sourcePath) => {
78
- const scope = registeredScope(repository.path, sourcePath);
79
- return scope ? { ...repository, scope } : repository;
76
+ const scoped = (repository, sourcePath, workspacePaths, selectedWorkspacePaths) => {
77
+ const scope = workspacePaths?.length ? [...new Set(workspacePaths.flatMap(workspace => registeredScope(repository.path, workspace) ?? []))] : registeredScope(repository.path, sourcePath);
78
+ if (selectedWorkspacePaths) {
79
+ const selectedScope = [...new Set(selectedWorkspacePaths.flatMap(workspace => registeredScope(repository.path, workspace) ?? []))];
80
+ return { ...repository, ...(selectedScope.length ? { scope: selectedScope } : {}), ...(scope?.length ? { registeredScope: scope } : {}) };
81
+ }
82
+ return scope?.length ? { ...repository, scope } : repository;
80
83
  };
81
84
  const repositories = manifest
82
- ? manifest.repositories.map(repo => scoped({ id: repo.repositoryId, name: repo.name, path: (0, node_fs_1.realpathSync)(repo.worktreePath), baseSha: repo.baseSha }, repo.sourcePath))
83
- : [scoped({ id: run.repositoryId ?? (run.spec?.repositoryIds?.length === 1 ? run.spec.repositoryIds[0] : run.projectId ? `primary-${run.projectId}` : 'primary'), name: (0, node_path_1.basename)(input.repoDir ?? cwd), path: (0, node_fs_1.realpathSync)(input.repoDir ?? cwd) }, input.sourcePath)];
85
+ ? manifest.repositories.map(repo => scoped({ id: repo.repositoryId, name: repo.name, path: (0, node_fs_1.realpathSync)(repo.worktreePath), baseSha: repo.baseSha }, repo.workspacePath ?? repo.sourcePath, repo.workspacePaths, repo.selectedWorkspacePaths))
86
+ : [scoped({ id: run.repositoryId ?? (run.spec?.repositoryIds?.length === 1 ? run.spec.repositoryIds[0] : run.projectId ? `primary-${run.projectId}` : 'primary'), name: (0, node_path_1.basename)(input.repoDir ?? cwd), path: (0, node_fs_1.realpathSync)(input.repoDir ?? cwd) }, input.sourcePath, input.workspacePaths, input.selectedWorkspacePaths)];
84
87
  if (!repositories.length || repositories.some(repo => !SAFE_ID.test(repo.id)))
85
88
  throw new Error('Invalid Core repository scope');
86
89
  const artifactRepositoryId = manifest?.artifactRepositoryId ?? repositories[0].id;
@@ -127,7 +130,13 @@ function prepareCoreExecution(input) {
127
130
  }
128
131
  const env = { ...input.env, SPECRAILS_EXECUTION_CONTEXT: contextPath };
129
132
  const promptPrefix = run.verificationStep ? coreVerificationContext(contextPath, cwd, env, run.runId) : '';
130
- return { env, contextPath, promptPrefix };
133
+ const frozen = JSON.parse((0, node_fs_1.readFileSync)(contextPath, 'utf8'));
134
+ const workspaceBriefing = [
135
+ 'Code workspaces frozen for this run (the project folder and the internal agent workspace are not test targets):',
136
+ ...frozen.repositories.map(repo => JSON.stringify({ repositoryId: repo.id, name: repo.name, checkout: repo.path, workspaces: (repo.scope ?? ['.']).map(scope => (0, node_path_1.resolve)(repo.path, scope)) })),
137
+ 'Run tests, builds and other checks in these code workspace directories. For a manual shell command, set its cwd explicitly or cd to the exact workspace first. Do not use the parent project directory or another workspace as a fallback. For multiple workspaces, verify each selected workspace separately. If a workspace or its test command is unavailable, report the blocker.',
138
+ ].join('\n');
139
+ return { env, contextPath, promptPrefix: [workspaceBriefing, promptPrefix].filter(Boolean).join('\n\n'), workspaceBriefing };
131
140
  }
132
141
  /** Ask the installed Core runtime to validate its own receipts. Desktop never
133
142
  * treats an agent-authored summary or a JSON 'pass' field as verification.
@@ -73,7 +73,7 @@ class CoreUpdateManager {
73
73
  this.latestVersion != null &&
74
74
  currentVersion != null &&
75
75
  ((0, semver_lite_1.isNewer)(this.latestVersion, currentVersion) || selected.error !== null && this.latestVersion === currentVersion) &&
76
- Number(this.latestVersion.split('.')[0]) <= 5;
76
+ (0, core_package_1.isSupportedCoreVersion)(this.latestVersion);
77
77
  return {
78
78
  available: this.isAvailable(),
79
79
  currentVersion,
@@ -1608,6 +1608,15 @@ const MIGRATIONS = [
1608
1608
  (db) => {
1609
1609
  db.exec(`UPDATE rails SET mode='implement' WHERE mode IN ('batch-implement','batch')`);
1610
1610
  },
1611
+ // Migration 71: source-bound rail relaunch. NULL preserves historical rows;
1612
+ // new attempts freeze their admitted launch options before agent execution.
1613
+ (db) => {
1614
+ for (const table of ['rail_pr_deliveries', 'loop_runs']) {
1615
+ const columns = db.prepare(`PRAGMA table_info(${table})`).all();
1616
+ if (!columns.some(column => column.name === 'launch_config_json'))
1617
+ db.exec(`ALTER TABLE ${table} ADD COLUMN launch_config_json TEXT`);
1618
+ }
1619
+ },
1611
1620
  ];
1612
1621
  function applyMigrations(db) {
1613
1622
  // Ensure the migrations table exists (migration 1 creates it, but we need
@@ -675,6 +675,14 @@ function applyDesktopMigrations(db) {
675
675
  UPDATE loops SET published_graph = graph WHERE status = 'published' AND published_graph IS NULL;
676
676
  `);
677
677
  },
678
+ // 32: explicit code workspace; old memberships continue to use their root.
679
+ () => {
680
+ const columns = db.prepare('PRAGMA table_info(project_repositories)').all().map(column => column.name);
681
+ if (!columns.includes('workspace_path'))
682
+ db.exec('ALTER TABLE project_repositories ADD COLUMN workspace_path TEXT');
683
+ if (!columns.includes('workspace_paths'))
684
+ db.exec('ALTER TABLE project_repositories ADD COLUMN workspace_paths TEXT');
685
+ },
678
686
  ];
679
687
  (0, sqlite_migrations_1.applyNumberedMigrations)(db, migrations);
680
688
  }
@@ -726,15 +734,15 @@ function addProject(db, project) {
726
734
  const provider = project.provider ?? providers[0];
727
735
  // Low-level callers may register unavailable legacy roots. HTTP validates all
728
736
  // roots first; membership and project rows are still committed atomically here.
729
- const roots = [(0, project_repositories_1.inspectRepositoryPath)({ path: project.path, name: project.name }, false), ...(project.repositories ?? []).map((input) => (0, project_repositories_1.inspectRepositoryPath)(input))];
737
+ const roots = [(0, project_repositories_1.inspectRepositoryPath)({ path: project.path, name: project.name, workspacePaths: project.workspacePaths }, project.workspacePaths !== undefined), ...(project.repositories ?? []).map((input) => (0, project_repositories_1.inspectRepositoryPath)(input))];
730
738
  (0, project_repositories_1.assertDistinctRepositories)(roots);
731
739
  db.transaction(() => {
732
740
  db.prepare(`
733
741
  INSERT INTO projects (id, slug, name, path, db_path, provider, providers)
734
742
  VALUES (?, ?, ?, ?, ?, ?, ?)
735
743
  `).run(project.id, project.slug, project.name, project.path, dbPath, provider, JSON.stringify(providers));
736
- const insert = db.prepare(`INSERT INTO project_repositories (id, project_id, name, path, canonical_key, git_identity, is_primary, kind, integration_branch) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`);
737
- roots.forEach((root, index) => insert.run(index === 0 ? `primary-${project.id}` : (0, crypto_1.randomUUID)(), project.id, root.name ?? path_1.default.basename(root.path), index === 0 ? project.path : root.path, root.canonicalKey, root.gitIdentity, index === 0 ? 1 : 0, root.kind, root.integrationBranch ?? null));
744
+ const insert = db.prepare(`INSERT INTO project_repositories (id, project_id, name, path, canonical_key, git_identity, is_primary, kind, integration_branch, workspace_path, workspace_paths) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
745
+ roots.forEach((root, index) => insert.run(index === 0 ? `primary-${project.id}` : (0, crypto_1.randomUUID)(), project.id, root.name ?? path_1.default.basename(root.path), index === 0 ? project.path : root.path, root.canonicalKey, root.gitIdentity, index === 0 ? 1 : 0, root.kind, root.integrationBranch ?? null, root.workspacePath ?? null, root.workspacePaths ? JSON.stringify(root.workspacePaths) : null));
738
746
  })();
739
747
  return getProject(db, project.id);
740
748
  }
@@ -742,7 +750,9 @@ function listProjectRepositories(db, projectId) {
742
750
  return db.prepare('SELECT * FROM project_repositories WHERE project_id = ? ORDER BY is_primary DESC, added_at, rowid').all(projectId).map((row) => ({
743
751
  id: row.id, projectId: row.project_id, name: row.name, path: row.path,
744
752
  isPrimary: row.is_primary === 1, kind: row.kind, integrationBranch: row.integration_branch,
745
- addedAt: row.added_at, available: (0, project_repositories_1.repositoryAvailable)(row.path),
753
+ addedAt: row.added_at, available: (0, project_repositories_1.repositoryAvailable)(row.path) && (row.workspace_paths ? JSON.parse(row.workspace_paths).every(project_repositories_1.repositoryAvailable) : (0, project_repositories_1.repositoryAvailable)(row.workspace_path ?? row.path)),
754
+ ...(row.workspace_path ? { workspacePath: row.workspace_path } : {}),
755
+ ...(row.workspace_paths ? { workspacePaths: JSON.parse(row.workspace_paths) } : {}),
746
756
  }));
747
757
  }
748
758
  function addProjectRepository(db, projectId, input) {
@@ -752,8 +762,8 @@ function addProjectRepository(db, projectId, input) {
752
762
  const existing = listProjectRepositories(db, projectId).map((repo) => (0, project_repositories_1.inspectRepositoryPath)(repo, false));
753
763
  (0, project_repositories_1.assertDistinctRepositories)([...existing, root]);
754
764
  const id = (0, crypto_1.randomUUID)();
755
- db.prepare(`INSERT INTO project_repositories (id, project_id, name, path, canonical_key, git_identity, kind, integration_branch) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
756
- .run(id, projectId, root.name ?? path_1.default.basename(root.path), root.path, root.canonicalKey, root.gitIdentity, root.kind, root.integrationBranch ?? null);
765
+ db.prepare(`INSERT INTO project_repositories (id, project_id, name, path, canonical_key, git_identity, kind, integration_branch, workspace_path, workspace_paths) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
766
+ .run(id, projectId, root.name ?? path_1.default.basename(root.path), root.path, root.canonicalKey, root.gitIdentity, root.kind, root.integrationBranch ?? null, root.workspacePath ?? null, root.workspacePaths ? JSON.stringify(root.workspacePaths) : null);
757
767
  return listProjectRepositories(db, projectId).find((repo) => repo.id === id);
758
768
  }
759
769
  function updateProjectRepository(db, projectId, repositoryId, input) {
@@ -763,15 +773,18 @@ function updateProjectRepository(db, projectId, repositoryId, input) {
763
773
  throw new project_repositories_1.RepositoryValidationError('Repository does not belong to this project', 'repository_not_found', 404);
764
774
  if (current.isPrimary && input.path !== undefined)
765
775
  throw new project_repositories_1.RepositoryValidationError('The primary project path cannot be relocated here', 'primary_repository_protected', 409);
766
- const next = { ...current, ...input };
767
- const root = (0, project_repositories_1.inspectRepositoryPath)(next, input.path !== undefined);
776
+ const next = { ...current, ...input,
777
+ ...(input.workspacePaths !== undefined && input.workspacePath === undefined ? { workspacePath: null } : {}),
778
+ ...(input.workspacePath !== undefined && input.workspacePaths === undefined ? { workspacePaths: null } : {}),
779
+ };
780
+ const root = (0, project_repositories_1.inspectRepositoryPath)(next, input.path !== undefined || input.workspacePath !== undefined || input.workspacePaths !== undefined);
768
781
  if (input.path === undefined && !(0, project_repositories_1.repositoryAvailable)(current.path)) {
769
782
  root.kind = current.kind;
770
783
  root.gitIdentity = db.prepare('SELECT git_identity FROM project_repositories WHERE id = ?').get(repositoryId).git_identity;
771
784
  }
772
785
  (0, project_repositories_1.assertDistinctRepositories)([...repositories.filter((repo) => repo.id !== repositoryId).map((repo) => (0, project_repositories_1.inspectRepositoryPath)(repo, false)), root]);
773
- db.prepare('UPDATE project_repositories SET name = ?, path = ?, canonical_key = ?, git_identity = ?, kind = ?, integration_branch = ? WHERE project_id = ? AND id = ?')
774
- .run(root.name ?? current.name, input.path === undefined ? current.path : root.path, root.canonicalKey, root.gitIdentity, root.kind, root.integrationBranch ?? null, projectId, repositoryId);
786
+ db.prepare('UPDATE project_repositories SET name = ?, path = ?, canonical_key = ?, git_identity = ?, kind = ?, integration_branch = ?, workspace_path = ?, workspace_paths = ? WHERE project_id = ? AND id = ?')
787
+ .run(root.name ?? current.name, input.path === undefined ? current.path : root.path, root.canonicalKey, root.gitIdentity, root.kind, root.integrationBranch ?? null, root.workspacePath ?? null, root.workspacePaths ? JSON.stringify(root.workspacePaths) : null, projectId, repositoryId);
775
788
  return listProjectRepositories(db, projectId).find((repo) => repo.id === repositoryId);
776
789
  }
777
790
  /** Removes the association only. Operational reference guards belong to ProjectRegistry. */
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.createDesktopRouter = createDesktopRouter;
7
+ const http_1 = require("./modules/subscription-usage/adapters/http");
7
8
  const runtime_role_prompts_router_1 = require("./runtime-role-prompts-router");
8
9
  const express_1 = require("express");
9
10
  const agent_runtime_settings_1 = require("./modules/agent-runtime/runtime/agent-runtime-settings");
@@ -319,8 +320,10 @@ function publicWebhook(row) {
319
320
  const { secret: _secret, ...rest } = row;
320
321
  return { ...rest, hasSecret: row.secret.length > 0 };
321
322
  }
322
- function createDesktopRouter(registry, broadcast) {
323
+ function createDesktopRouter(registry, broadcast, usageService) {
323
324
  const router = (0, express_1.Router)();
325
+ if (usageService)
326
+ (0, http_1.registerUsageRoutes)(router, usageService);
324
327
  // Loops (global, cross-project library) — /api/loops*. Registered here so the
325
328
  // routes live on the global `/api` router (loops are NOT project-scoped). The
326
329
  // A loop is "running" iff any project's per-project DB has an in-flight
@@ -476,7 +479,7 @@ function createDesktopRouter(registry, broadcast) {
476
479
  });
477
480
  // POST /api/projects — register a new project by path
478
481
  router.post('/projects', async (req, res) => {
479
- const { path: projectPath, name, provider, providers: providersRaw, repositories: repositoriesRaw } = req.body ?? {};
482
+ const { path: projectPath, name, provider, providers: providersRaw, repositories: repositoriesRaw, workspacePaths } = req.body ?? {};
480
483
  if (!projectPath || typeof projectPath !== 'string') {
481
484
  res.status(400).json({ error: 'path is required' });
482
485
  return;
@@ -573,7 +576,7 @@ function createDesktopRouter(registry, broadcast) {
573
576
  if (repositoriesRaw !== undefined && !Array.isArray(repositoriesRaw))
574
577
  throw new project_repositories_1.RepositoryValidationError('repositories must be an array');
575
578
  repositories = (repositoriesRaw ?? []).map((input) => (0, project_repositories_1.inspectRepositoryPath)(input));
576
- const primary = (0, project_repositories_1.inspectRepositoryPath)({ path: canonicalPath });
579
+ const primary = (0, project_repositories_1.inspectRepositoryPath)({ path: canonicalPath, workspacePaths });
577
580
  (0, project_repositories_1.assertDistinctRepositories)([primary, ...repositories.map((input) => (0, project_repositories_1.inspectRepositoryPath)(input))]);
578
581
  if (repositories.some((repository) => !isPathSafe(repository.path)))
579
582
  throw new project_repositories_1.RepositoryValidationError('Registering system directories is not allowed');
@@ -609,6 +612,7 @@ function createDesktopRouter(registry, broadcast) {
609
612
  provider: providers[0],
610
613
  providers,
611
614
  repositories,
615
+ workspacePaths,
612
616
  });
613
617
  broadcast({
614
618
  type: 'desktop.project_added',
@@ -681,7 +685,7 @@ function createDesktopRouter(registry, broadcast) {
681
685
  try {
682
686
  const body = req.body ?? {};
683
687
  const input = {};
684
- for (const key of ['path', 'name', 'integrationBranch'])
688
+ for (const key of ['path', 'name', 'integrationBranch', 'workspacePath', 'workspacePaths'])
685
689
  if (Object.prototype.hasOwnProperty.call(body, key))
686
690
  Object.assign(input, { [key]: body[key] });
687
691
  if (input.path !== undefined) {
@@ -1,12 +1,13 @@
1
1
  "use strict";
2
- // Note: the pkg-binary native-addon hijacks (better_sqlite3.node, node-pty, pty.node)
3
- // used to live here but had to move to an esbuild `banner.js` in scripts/build-sidecar.mjs
4
- // so they run BEFORE esbuild's top-of-bundle `require('node-pty')` statement.
5
- // See the banner in that script for the actual patches.
6
2
  var __importDefault = (this && this.__importDefault) || function (mod) {
7
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
8
4
  };
9
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ const composition_1 = require("./modules/subscription-usage/runtime/composition");
7
+ // Note: the pkg-binary native-addon hijacks (better_sqlite3.node, node-pty, pty.node)
8
+ // used to live here but had to move to an esbuild `banner.js` in scripts/build-sidecar.mjs
9
+ // so they run BEFORE esbuild's top-of-bundle `require('node-pty')` statement.
10
+ // See the banner in that script for the actual patches.
10
11
  const http_1 = __importDefault(require("http"));
11
12
  const path_1 = __importDefault(require("path"));
12
13
  const fs_1 = __importDefault(require("fs"));
@@ -275,6 +276,7 @@ function broadcast(msg) {
275
276
  let _getProjectCount = () => 0;
276
277
  /** Captured by the Super-mode bootstrap block so graceful shutdown can tear down every
277
278
  * project's spawners (rail/chat children) instead of orphaning them. */
279
+ let _usageService = null;
278
280
  let _registry = null;
279
281
  /** The mobile companion gateway (off by default); torn down on shutdown. */
280
282
  let _mobileGateway = null;
@@ -672,7 +674,9 @@ function applyPtyWsRateLimiting(ws) {
672
674
  // GET /api/projects, DELETE /api/projects/:id) are handled here, while
673
675
  // everything else under /api/projects/:projectId/* falls through to the
674
676
  // project router.
675
- app.use('/api', (0, desktop_router_1.createDesktopRouter)(registry, broadcast));
677
+ const usageService = (0, composition_1.createDesktopUsageService)();
678
+ _usageService = usageService;
679
+ app.use('/api', (0, desktop_router_1.createDesktopRouter)(registry, broadcast, usageService));
676
680
  // Per-project routes under /api/projects/:projectId/*
677
681
  app.use('/api/projects', (0, project_router_1.createProjectRouter)(registry));
678
682
  // Return 410 Gone for the old per-project hook endpoint in Super mode
@@ -780,6 +784,7 @@ async function shutdown() {
780
784
  // skip Agent Chat, and Headroom must remain available until all routed AI
781
785
  // children have received their termination signal.
782
786
  await Promise.allSettled([
787
+ Promise.resolve().then(() => _usageService?.dispose()),
783
788
  Promise.resolve().then(() => _registry?.shutdown()),
784
789
  Promise.resolve().then(() => (0, terminal_manager_1.getTerminalManager)().shutdown()),
785
790
  Promise.resolve().then(() => _mobileGateway?.stop()),
@@ -28,6 +28,7 @@ Codex, Gemini, Kimi Code) to implement specs.
28
28
  \`specrails_projects(get)\` includes the repo's absolute path and availability.
29
29
  A registered project with an unavailable database still exists; never create
30
30
  a duplicate or interpret unavailable data as an empty backlog.
31
+ - **Code workspace**: a configured code directory inside a repository. A project can have one or many. Discover \`workspacePaths\` with \`specrails_projects(get/repositories)\`; configure them with \`repository_add/repository_update\` (write permission), or register them through \`specrails_setup(add_project)\`. Independent Git roots are separate repository memberships. Launch rails or standalone loops with \`workspaceSelection\` mapping repository IDs to registered paths; omission selects all configured workspaces. Tests run within the selected directories, never implicitly in the project parent.
31
32
  - **Repository**: a stable membership within a project, addressed by \`repositoryId\`.
32
33
  Discover the inventory in \`specrails_projects(get)\` or \`specrails_context(overview)\`.
33
34
  A member is a Git repository or a non-Git context folder. File paths are relative
@@ -138,7 +138,7 @@ function jobsTools() {
138
138
  text: zod_1.z.string().optional().describe('Prompt text for interactive_turn'),
139
139
  chatId: zod_1.z.string().optional().describe('Conversation id for background_logs ownership. background_start/background_kill ignore this field and use the authenticated in-app capability.'),
140
140
  repositoryId: zod_1.z.string().min(1).optional().describe('spawn/background_start: repository membership ID; required when the project has several repositories. spawn accepts only the primary repository; use specrails_rails launch or specrails_loops run for other repositories.'),
141
- cwd: zod_1.z.string().optional().describe('Optional cwd for background_start; resolved inside the selected repository root'),
141
+ cwd: zod_1.z.string().optional().describe('Optional cwd for background_start; resolved inside the selected repository root. For tests/dev servers, discover workspacePaths with specrails_projects get and pass the intended code workspace cwd explicitly'),
142
142
  pid: zod_1.z.number().int().positive().optional().describe('Background process pid for background_logs/background_kill'),
143
143
  processId: zod_1.z.string().min(1).max(200).optional().describe('Background execution UUID returned by background_start/list. Pass with pid on logs/kill to avoid targeting a reused PID.'),
144
144
  includeFinished: zod_1.z.boolean().optional().describe('background_list: include persisted completed/disconnected executions (default true). Active applications are listed first; use limit/offset for older history.'),
@@ -106,6 +106,7 @@ function loopsTools() {
106
106
  .describe('Project id (run / run_get only — defaults to the active project; ignored by every other action)'),
107
107
  loopId: zod_1.z.string().optional().describe('Loop id (for get / update / publish / unpublish / duplicate / restore_builtin / delete / run — must be Published for run). Built-ins use their factory id, e.g. factory:implement'),
108
108
  repositoryIds: zod_1.z.array(zod_1.z.string().min(1)).min(1).max(50).optional().describe('run only: repository memberships to include in one coordinated standalone execution; omission retains primary-only behavior.'),
109
+ workspaceSelection: zod_1.z.record(zod_1.z.string(), zod_1.z.array(zod_1.z.string().min(1)).min(1).max(50)).optional().describe('run only: selected registered code workspace paths per selected repository ID. Omission uses all configured workspaces.'),
109
110
  loopRunId: zod_1.z.string().optional().describe('Loop run id (for run_get — returned by run)'),
110
111
  constantId: zod_1.z.string().optional().describe('Constant id (for constant_update / constant_delete)'),
111
112
  templateId: zod_1.z.string().optional().describe('Template id (for from_template)'),
@@ -270,6 +271,7 @@ function loopsTools() {
270
271
  const r = await (0, types_1.apiCall)(ctx, 'POST', `${base}/loop-runs`, {
271
272
  loopId,
272
273
  ...(args.repositoryIds === undefined ? {} : { repositoryIds: args.repositoryIds }),
274
+ ...(args.workspaceSelection === undefined ? {} : { workspaceSelection: args.workspaceSelection }),
273
275
  ...(provider !== undefined ? { provider } : {}),
274
276
  ...(model !== undefined ? { model } : {}),
275
277
  ...(effort !== undefined ? { reasoning_effort: effort } : {}),
@@ -1,11 +1,8 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.metaTools = metaTools;
7
4
  const zod_1 = require("zod");
8
- const node_path_1 = __importDefault(require("node:path"));
5
+ const project_repositories_1 = require("../../project-repositories");
9
6
  const zod_json_schema_compat_js_1 = require("@modelcontextprotocol/sdk/server/zod-json-schema-compat.js");
10
7
  const guide_1 = require("../guide");
11
8
  const types_1 = require("./types");
@@ -162,9 +159,11 @@ function metaTools(getSpecs) {
162
159
  handler: (ctx, args) => {
163
160
  if (args.path !== undefined && args.projectId !== undefined)
164
161
  throw new Error('Provide either path or projectId, not both.');
165
- const project = args.path
166
- ? ctx.registry.listProjects().find(p => node_path_1.default.resolve(p.path) === node_path_1.default.resolve(String(args.path)))
167
- : typeof args.projectId === 'string' ? ctx.registry.getProjectRow(args.projectId) : undefined;
162
+ const key = args.path ? (0, project_repositories_1.repositoryPathKey)((0, project_repositories_1.canonicalRepositoryPath)(String(args.path))) : undefined;
163
+ const matches = key ? ctx.registry.listProjects().filter(project => (0, project_repositories_1.getProjectRepositories)(project).some(repository => [repository.path, ...(repository.workspacePaths ?? (repository.workspacePath ? [repository.workspacePath] : []))].some(root => (0, project_repositories_1.repositoryPathKey)((0, project_repositories_1.canonicalRepositoryPath)(root)) === key))) : [];
164
+ if (matches.length > 1)
165
+ throw new Error('Ambiguous project path; provide an explicit projectId.');
166
+ const project = key ? matches[0] : typeof args.projectId === 'string' ? ctx.registry.getProjectRow(args.projectId) : undefined;
168
167
  if (args.path && !project)
169
168
  throw new Error(`No project registered at path "${args.path}".`);
170
169
  if (args.projectId && !project)
@@ -25,17 +25,42 @@ function projectsTools() {
25
25
  name: 'specrails_projects',
26
26
  title: 'Projects',
27
27
  description: 'List and inspect registered Specrails projects, resolve a project by filesystem path, or unregister one. ' +
28
- 'Actions: list, get, resolve (by path), unregister (destructive — removes the project and its workspace).',
28
+ 'Actions: list, get, resolve (by project/repository/workspace path), repositories (inventory), repository_add / repository_update (write, configure registered code workspacePaths), repository_remove (destructive), unregister (destructive — removes the project and its workspace).',
29
29
  hintTier: 'read',
30
- tier: (args) => (args.action === 'unregister' ? 'destructive' : 'read'),
30
+ tier: (args) => ['unregister', 'repository_remove'].includes(String(args.action)) ? 'destructive' : ['repository_add', 'repository_update'].includes(String(args.action)) ? 'write' : 'read',
31
31
  inputSchema: {
32
- action: zod_1.z.enum(['list', 'get', 'resolve', 'unregister']).describe('Operation to perform'),
33
- projectId: zod_1.z.string().optional().describe('Project id (for get / unregister)'),
34
- path: zod_1.z.string().optional().describe('Filesystem path (for resolve)'),
32
+ action: zod_1.z.enum(['list', 'get', 'resolve', 'repositories', 'repository_add', 'repository_update', 'repository_remove', 'unregister']).describe('Operation to perform'),
33
+ projectId: zod_1.z.string().optional().describe('Project id; defaults to the active project for get and repository operations; required for unregister'),
34
+ path: zod_1.z.string().optional().describe('Filesystem path for resolve or repository_add/update; repository root retains its Git/delivery identity'),
35
+ repositoryId: zod_1.z.string().min(1).optional().describe('Required for repository_update/remove; discover from repositories/get'),
36
+ name: zod_1.z.string().min(1).optional().describe('Repository display name'),
37
+ integrationBranch: zod_1.z.string().nullable().optional().describe('Repository integration branch; null clears it'),
38
+ workspacePaths: zod_1.z.array(zod_1.z.string().min(1)).min(1).max(50).nullable().optional().describe('repository_add/update: code directories inside the repository, absolute or root-relative. null resets to repository root. Independent Git roots need separate memberships. Changes are rejected while active runs depend on this scope.'),
35
39
  },
36
40
  handler: (ctx, args) => {
37
41
  const action = args.action;
38
42
  switch (action) {
43
+ case 'repositories':
44
+ return (0, types_1.apiCall)(ctx, 'GET', `${(0, types_1.projectPath)(ctx, args.projectId)}/repositories`);
45
+ case 'repository_add':
46
+ case 'repository_update':
47
+ case 'repository_remove': {
48
+ const base = `${(0, types_1.projectPath)(ctx, args.projectId)}/repositories`;
49
+ const id = args.repositoryId;
50
+ if (action !== 'repository_add' && !id)
51
+ throw new Error(`${action} requires repositoryId.`);
52
+ if (action === 'repository_remove')
53
+ return (0, types_1.apiCall)(ctx, 'DELETE', `${base}/${encodeURIComponent(id)}`);
54
+ if (action === 'repository_add' && !args.path)
55
+ throw new Error('repository_add requires path.');
56
+ const body = {};
57
+ for (const key of ['path', 'name', 'integrationBranch', 'workspacePaths'])
58
+ if (args[key] !== undefined)
59
+ body[key] = args[key];
60
+ if (!Object.keys(body).length)
61
+ throw new Error('repository_update requires at least one field to update.');
62
+ return (0, types_1.apiCall)(ctx, action === 'repository_add' ? 'POST' : 'PATCH', action === 'repository_add' ? base : `${base}/${encodeURIComponent(id)}`, body);
63
+ }
39
64
  case 'list':
40
65
  return ctx.registry.listProjects().map((p) => ({ ...serializeProject(p), available: !!ctx.registry.getContext(p.id) }));
41
66
  case 'get': {
@@ -53,7 +78,7 @@ function projectsTools() {
53
78
  throw new Error('resolve requires a "path".');
54
79
  const selected = args.projectId;
55
80
  const key = (0, project_repositories_1.repositoryPathKey)((0, project_repositories_1.canonicalRepositoryPath)(p));
56
- const matches = ctx.registry.listProjects().filter(row => !selected || row.id === selected).flatMap(project => (0, project_repositories_1.getProjectRepositories)(project).filter(repository => (0, project_repositories_1.repositoryPathKey)((0, project_repositories_1.canonicalRepositoryPath)(repository.path)) === key)
81
+ const matches = ctx.registry.listProjects().filter(row => !selected || row.id === selected).flatMap(project => (0, project_repositories_1.getProjectRepositories)(project).filter(repository => [repository.path, ...(repository.workspacePaths ?? (repository.workspacePath ? [repository.workspacePath] : []))].some(root => (0, project_repositories_1.repositoryPathKey)((0, project_repositories_1.canonicalRepositoryPath)(root)) === key))
57
82
  .map(repository => ({ project, repository })));
58
83
  if (matches.length > 1)
59
84
  return { resolved: false, ambiguous: true, matches: matches.map(({ project, repository }) => ({ projectId: project.id, projectName: project.name, repositoryId: repository.id, repositoryName: repository.name })) };
@@ -49,7 +49,7 @@ function railsTools() {
49
49
  'When launched from the in-app agent chat without an explicit aiEngine, the engine defaults to your conversation\'s provider. On that same provider, an omitted model inherits the conversation model; an explicit model wins (pass aiEngine to override; launch_all always uses each rail\'s stored engine). ' +
50
50
  'For Implement, the selected launch provider applies to ALL runtime roles (architect, developer, reviewer); an explicit launch model/effort also applies to each role. Resuming an existing execution keeps its original frozen role configuration; start a new launch to change providers. ' +
51
51
  'User-facing naming: call the free-form autonomous mode "Freestyle"; use "freestyle" as the canonical API enum value for that same capability. ' +
52
- 'For small OpenSpec-governed work, recommend "SDD Quick (OpenSpec)" and launch with mode "loop" plus loopId "factory:sdd-quick-openspec"; keep Freestyle for ticket-local implementation-only work. ' +
52
+ 'Always honor an explicit workflow selection, including Implement for addenda or delivery changes. Without a preference, for small OpenSpec-governed work, recommend "SDD Quick (OpenSpec)" and launch with mode "loop" plus loopId "factory:sdd-quick-openspec"; keep Freestyle for ticket-local implementation-only work. ' +
53
53
  'NAMING: railIndex is the 0-BASED internal identity; the dashboard shows rails 1-based ("Rail N" = railIndex N-1). When talking to the user, ALWAYS say "Rail <railIndex + 1>" (or the rail\'s custom name) — results include railLabel with the correct user-facing label.',
54
54
  hintTier: 'read',
55
55
  tier: (a) => {
@@ -68,6 +68,7 @@ function railsTools() {
68
68
  .describe('Operation to perform'),
69
69
  projectId: zod_1.z.string().optional().describe('Project id (defaults to the active project)'),
70
70
  repositoryIds: zod_1.z.array(zod_1.z.string().min(1)).min(1).max(50).optional().describe('launch only: explicit affected repository memberships for the coordinated run; must include every repository required by the assigned specs. Omission uses the assigned specs\' repository selections.'),
71
+ workspaceSelection: zod_1.z.record(zod_1.z.string(), zod_1.z.array(zod_1.z.string().min(1)).min(1)).optional().describe('launch only: map selected repository IDs to registered code workspace paths; discover workspacePaths through project context. Omission verifies all configured code workspaces. Never use the parent project path unless it is itself a registered code workspace.'),
71
72
  repositoryId: zod_1.z.string().min(1).optional().describe('review_packet only: member of a grouped delivery to inspect; omission returns the public parent with grouped outcomes.'),
72
73
  railIndex: zod_1.z
73
74
  .number()
@@ -129,11 +130,11 @@ function railsTools() {
129
130
  revisionOfDeliveryId: zod_1.z
130
131
  .string()
131
132
  .optional()
132
- .describe('Revise a delivery that is ALREADY awaiting the user\'s decision (launch). Continues with Quick SDD (the Revision loop is retired): pass its prDeliveryId (from rails.prDeliveries) together with revisionNote. Use it whenever the user asks for a change to work you already delivered — never publish/discard/merge first. The rail must still carry exactly that delivery\'s specs.'),
133
+ .describe('Revise a delivery that is ALREADY awaiting the user\'s decision (launch). Continues with the explicitly selected loop, defaulting to Quick SDD (the Revision loop is retired): pass its prDeliveryId (from rails.prDeliveries) together with revisionNote. Use it whenever the user asks for a change to work you already delivered — never publish/discard/merge first. The rail must still carry exactly that delivery\'s specs.'),
133
134
  revisionNote: zod_1.z
134
135
  .string()
135
136
  .optional()
136
- .describe('What to change, in the user\'s own words (required with revisionOfDeliveryId). It is injected into every Quick SDD phase and shown on the updated review packet as "what you asked to change".'),
137
+ .describe('What to change, in the user\'s own words (required with revisionOfDeliveryId). It is injected into every selected workflow phase and shown on the updated review packet as "what you asked to change".'),
137
138
  followUp: zod_1.z
138
139
  .object({
139
140
  comments: zod_1.z.array(zod_1.z.object({
@@ -257,6 +258,8 @@ function railsTools() {
257
258
  body.targetPrNumber = args.targetPrNumber;
258
259
  if (args.repositoryIds !== undefined)
259
260
  body.repositoryIds = args.repositoryIds;
261
+ if (args.workspaceSelection !== undefined)
262
+ body.workspaceSelection = args.workspaceSelection;
260
263
  if (args.baseBranch !== undefined)
261
264
  body.baseBranch = args.baseBranch;
262
265
  // Revision of a delivery already awaiting the user's decision: the
@@ -88,6 +88,8 @@ function setupTools() {
88
88
  .array(zod_1.z.string())
89
89
  .optional()
90
90
  .describe('add_project: providers to install (first = primary/default); de-duped server-side'),
91
+ workspacePaths: zod_1.z.array(zod_1.z.string().min(1)).min(1).max(50).optional().describe('add_project: code workspace directories inside the primary project root'),
92
+ repositories: zod_1.z.array(zod_1.z.object({ path: zod_1.z.string().min(1), name: zod_1.z.string().optional(), workspacePaths: zod_1.z.array(zod_1.z.string().min(1)).min(1).max(50).optional(), integrationBranch: zod_1.z.string().nullable().optional() })).max(50).optional().describe('add_project: additional repository memberships with their code workspaces'),
91
93
  // install_config
92
94
  config: zod_1.z
93
95
  .record(zod_1.z.unknown())
@@ -115,6 +117,10 @@ function setupTools() {
115
117
  body.provider = args.provider;
116
118
  if (Array.isArray(args.providers))
117
119
  body.providers = args.providers;
120
+ if (args.workspacePaths !== undefined)
121
+ body.workspacePaths = args.workspacePaths;
122
+ if (args.repositories !== undefined)
123
+ body.repositories = args.repositories;
118
124
  return (0, types_1.apiCall)(ctx, 'POST', '/projects', body);
119
125
  }
120
126
  // ─── Per-project ────────────────────────────────────────────────────
@@ -137,6 +143,10 @@ function setupTools() {
137
143
  const body = {};
138
144
  if (Array.isArray(args.providers))
139
145
  body.providers = args.providers;
146
+ if (args.workspacePaths !== undefined)
147
+ body.workspacePaths = args.workspacePaths;
148
+ if (args.repositories !== undefined)
149
+ body.repositories = args.repositories;
140
150
  else if (typeof args.provider === 'string')
141
151
  body.provider = args.provider;
142
152
  else
@@ -313,7 +313,7 @@ function specsTools() {
313
313
  });
314
314
  return {
315
315
  ...r,
316
- hint: 'The addendum is open: launch with factory:sdd-quick-openspec. Rail launches route open addenda to Quick SDD, continue a same-spec pending delivery, and brief every AI step with the full delta and a fresh OpenSpec target. Do not use Revision or edit openspecChangeName. Do not edit the spec description to repeat it.',
316
+ hint: 'The addendum is open: respect the user\'s explicit loop selection, including factory:implement; otherwise recommend factory:sdd-quick-openspec. Rail launches continue a same-spec pending delivery, and brief every AI step with the full delta and a fresh OpenSpec target. Do not use Revision or edit openspecChangeName. Do not edit the spec description to repeat it.',
317
317
  };
318
318
  }
319
319
  case 'update_addendum': {
@@ -76,7 +76,10 @@ exports.PRICING = {
76
76
  // Opus 5.5: https://platform.claude.com/docs/en/models/opus-5-5/overview
77
77
  'claude:opus': { inputPer1M: 4.00, outputPer1M: 20.00, cacheReadPer1M: 0.20, cacheWritePer1M: 5.00, inputIncludesCacheReads: false, lastReviewedAt: '2026-09-24' },
78
78
  'claude:claude-opus-5-5': { inputPer1M: 4.00, outputPer1M: 20.00, cacheReadPer1M: 0.20, cacheWritePer1M: 5.00, inputIncludesCacheReads: false, lastReviewedAt: '2026-09-24' },
79
- 'claude:sonnet': { inputPer1M: 3.00, outputPer1M: 15.00, cacheReadPer1M: 0.30, cacheWritePer1M: 3.75, inputIncludesCacheReads: false, lastReviewedAt: '2026-07-02' },
79
+ 'claude:claude-sonnet-5': { inputPer1M: 3.00, outputPer1M: 15.00, cacheReadPer1M: 0.30, cacheWritePer1M: 3.75, inputIncludesCacheReads: false, lastReviewedAt: '2026-07-02' },
80
+ // Sonnet 5.5: https://platform.claude.com/docs/en/models/sonnet-5-5/overview
81
+ 'claude:sonnet': { inputPer1M: 2.00, outputPer1M: 10.00, cacheReadPer1M: 0.20, cacheWritePer1M: 2.50, inputIncludesCacheReads: false, lastReviewedAt: '2026-09-30' },
82
+ 'claude:claude-sonnet-5-5': { inputPer1M: 2.00, outputPer1M: 10.00, cacheReadPer1M: 0.20, cacheWritePer1M: 2.50, inputIncludesCacheReads: false, lastReviewedAt: '2026-09-30' },
80
83
  'claude:haiku': { inputPer1M: 1.00, outputPer1M: 5.00, cacheReadPer1M: 0.10, cacheWritePer1M: 1.25, inputIncludesCacheReads: false, lastReviewedAt: '2026-07-02' },
81
84
  'claude:fable': { inputPer1M: 10.00, outputPer1M: 50.00, cacheReadPer1M: 1.00, cacheWritePer1M: 12.50, inputIncludesCacheReads: false, lastReviewedAt: '2026-07-02' },
82
85
  };
@@ -96,6 +99,8 @@ function resolvePriceEntry(providerId, model) {
96
99
  // Keep the existing estimates for older concrete Opus generations.
97
100
  if (/^claude-opus-(?:4(?:-|$)|5$)/.test(model))
98
101
  return exports.PRICING['claude:claude-opus-5'];
102
+ if (/^claude-sonnet-(?:4(?:-|$)|5$)/.test(model))
103
+ return exports.PRICING['claude:claude-sonnet-5'];
99
104
  const family = CLAUDE_MODEL_FAMILY.exec(model);
100
105
  if (family)
101
106
  return exports.PRICING[`claude:${family[1]}`];