specrails-desktop 2.58.1 → 2.59.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 (425) hide show
  1. package/README.md +2 -5
  2. package/client/dist/assets/{ActivityFeedPage-BQGppIR0.js → ActivityFeedPage-DBFt8ZHP.js} +1 -1
  3. package/client/dist/assets/AgentBrowserCapture-DiQx0W67.js +1 -0
  4. package/client/dist/assets/AgentModeAnalyticsPane-BmDSXp_J.js +2 -0
  5. package/client/dist/assets/AgentModeCodePane-BLQLPSwr.js +2 -0
  6. package/client/dist/assets/{AgentModeJobsPane-DR_06oqH.js → AgentModeJobsPane-BbTXdKUa.js} +1 -1
  7. package/client/dist/assets/{AgentsPage-BKIKjm_1.js → AgentsPage-Dei1jGR8.js} +3 -3
  8. package/client/dist/assets/{AnalyticsPage-069npRVo.js → AnalyticsPage-BhkeGIGn.js} +1 -1
  9. package/client/dist/assets/CodePage-B_9yYWUx.js +3 -0
  10. package/client/dist/assets/{DesktopAnalyticsPage-qtWAPM47.js → DesktopAnalyticsPage-DAcfEJac.js} +1 -1
  11. package/client/dist/assets/DocsDialog-WCuiYgsc.js +11 -0
  12. package/client/dist/assets/DocsPage-bGVYTMTU.js +11 -0
  13. package/client/dist/assets/{ExportDropdown-BcMpvV89.js → ExportDropdown-CsodZ-yb.js} +1 -1
  14. package/client/dist/assets/InteractiveJobComposer-D5fh_eVL.js +19 -0
  15. package/client/dist/assets/JobDetailModal-DlpXF61O.js +1 -0
  16. package/client/dist/assets/JobDetailPage-2yR19_Lb.js +1 -0
  17. package/client/dist/assets/JobsPage-CS9IR6Dq.js +1 -0
  18. package/client/dist/assets/LoopBuilderPage-BwiAtmOr.js +1 -0
  19. package/client/dist/assets/{LoopPreviewModal-je5pSCf-.js → LoopPreviewModal--upwvgTv.js} +1 -1
  20. package/client/dist/assets/LoopWindowSurface-DRnUixtl.js +1 -0
  21. package/client/dist/assets/LoopsPage-Xm3nRToQ.js +2 -0
  22. package/client/dist/assets/MinimizedChatsContext-D_8L4EWZ.js +1 -0
  23. package/client/dist/assets/PluginsPage-CAFQ1B4Q.js +2 -0
  24. package/client/dist/assets/ProjectSettingsDialog-DYSO4v90.js +1 -0
  25. package/client/dist/assets/{RepositoryDeliveries-BiwJuS8r.js → RepositoryDeliveries-CbQmW7Qx.js} +1 -1
  26. package/client/dist/assets/RepositoryScopeSelector-DKzvIGSZ.js +2 -0
  27. package/client/dist/assets/ReviewPacketPage-SDBhYdQB.js +4 -0
  28. package/client/dist/assets/TemplatePreviewModal-DHBUds51.js +1 -0
  29. package/client/dist/assets/TicketDetailModalContext-UAeNm7BB.js +16 -0
  30. package/client/dist/assets/{Trans-D3AfDylf.js → Trans-DExd3Z00.js} +1 -1
  31. package/client/dist/assets/agent-BElLtdKP.js +1 -0
  32. package/client/dist/assets/agent-C-m9VugX.js +1 -0
  33. package/client/dist/assets/agent-CGSKxfZk.js +1 -0
  34. package/client/dist/assets/agent-CW74ODDP.js +1 -0
  35. package/client/dist/assets/agent-DeCAZDhL.js +1 -0
  36. package/client/dist/assets/agent-DrM4qned.js +1 -0
  37. package/client/dist/assets/agent-KdIXq3w5.js +1 -0
  38. package/client/dist/assets/agent-jK8mCisM.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-aw7H5ORC.js} +1 -1
  68. package/client/dist/assets/{getTimezoneOffsetInMilliseconds-DOGj1Mfz.js → getTimezoneOffsetInMilliseconds-NyC7gZJX.js} +1 -1
  69. package/client/dist/assets/index-CahchzS5.css +2 -0
  70. package/client/dist/assets/index-JSTZVJfe.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-DlXaRwA3.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-DXhb9FP4.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-BdcyVkcd.js} +1 -1
  124. package/client/dist/assets/subscriptionUsage-Cf87DeS_.js +1 -0
  125. package/client/dist/assets/subscriptionUsage-D6u7VBdz.js +1 -0
  126. package/client/dist/assets/subscriptionUsage-DQ02J25O.js +1 -0
  127. package/client/dist/assets/subscriptionUsage-DWF3zKp4.js +1 -0
  128. package/client/dist/assets/subscriptionUsage-DkIf6Zuh.js +1 -0
  129. package/client/dist/assets/subscriptionUsage-DoWQv7l1.js +1 -0
  130. package/client/dist/assets/subscriptionUsage-N9hB-ARf.js +1 -0
  131. package/client/dist/assets/subscriptionUsage-zRcRPHkP.js +1 -0
  132. package/client/dist/assets/tauri-shell-DE6LfGPw.js +2 -0
  133. package/client/dist/assets/{useDesktop-0unHDBJJ.js → useDesktop-D07TvesS.js} +1 -1
  134. package/client/dist/assets/{useRuntimeRuns-BN1rhyEm.js → useRuntimeRuns-pGLmtAC6.js} +1 -1
  135. package/client/dist/assets/useSharedWebSocket-TzKtPRp9.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/desktop-owned-workflows.md +121 -0
  277. package/docs/internals/operations-runbook.md +20 -0
  278. package/docs/internals/programmatic-agent-runtime.md +34 -28
  279. package/docs/internals/project-builder.md +34 -11
  280. package/docs/internals/source-map.md +51 -3
  281. package/docs/internals/spec-addenda.md +11 -5
  282. package/docs/local-providers.md +8 -13
  283. package/docs/mcp.md +1 -17
  284. package/docs/platforms/macos.md +0 -40
  285. package/docs/platforms/windows-parity.md +1 -1
  286. package/docs/platforms/windows.md +0 -8
  287. package/docs/running-pipelines.md +1 -46
  288. package/docs/subscription-usage.md +19 -0
  289. package/docs/terminal.md +0 -2
  290. package/docs/tracking-cost.md +2 -33
  291. package/package.json +1 -1
  292. package/server/dist/core-execution.js +24 -15
  293. package/server/dist/desktop-db.js +23 -10
  294. package/server/dist/desktop-router.js +8 -4
  295. package/server/dist/index.js +10 -5
  296. package/server/dist/mcp/guide.js +1 -0
  297. package/server/dist/mcp/tools/jobs.js +1 -1
  298. package/server/dist/mcp/tools/loops.js +2 -0
  299. package/server/dist/mcp/tools/meta.js +6 -7
  300. package/server/dist/mcp/tools/projects.js +31 -6
  301. package/server/dist/mcp/tools/rails.js +6 -3
  302. package/server/dist/mcp/tools/setup.js +10 -0
  303. package/server/dist/mcp/tools/specs.js +1 -1
  304. package/server/dist/modules/accounting/runtime/pricing.js +6 -1
  305. package/server/dist/modules/agent-runtime/runtime/agent-runtime-bridge.js +55 -12
  306. package/server/dist/modules/agent-runtime/runtime/agent-runtime-metrics.js +1 -1
  307. package/server/dist/modules/agent-runtime/runtime/agent-runtime-settings-router.js +1 -1
  308. package/server/dist/modules/agent-runtime/runtime/agent-runtime-settings.js +42 -0
  309. package/server/dist/modules/agent-runtime/runtime/agent-runtime-verification-suggestions.js +10 -4
  310. package/server/dist/modules/builder/runtime/milestone-chain.js +76 -10
  311. package/server/dist/modules/delivery/runtime/delivery-evidence.js +3 -2
  312. package/server/dist/modules/delivery/runtime/multi-repo-execution.js +3 -0
  313. package/server/dist/modules/delivery/runtime/rail-isolated-launch.js +1 -0
  314. package/server/dist/modules/delivery/runtime/rail-launch-parser.js +8 -0
  315. package/server/dist/modules/delivery/runtime/rails-router.js +48 -26
  316. package/server/dist/modules/delivery/runtime/review-packet.js +1 -1
  317. package/server/dist/modules/loops/runtime/loop-agent-defaults.json +6 -0
  318. package/server/dist/modules/loops/runtime/loop-agents.js +21 -0
  319. package/server/dist/modules/loops/runtime/loop-command-catalog.js +1 -1
  320. package/server/dist/modules/loops/runtime/loop-core-factory.js +19 -3
  321. package/server/dist/modules/loops/runtime/loop-definition.js +79 -12
  322. package/server/dist/modules/loops/runtime/loop-effect.js +1 -1
  323. package/server/dist/modules/loops/runtime/loop-executors.js +24 -9
  324. package/server/dist/modules/loops/runtime/loop-factory.js +1 -1
  325. package/server/dist/modules/loops/runtime/loop-graph.js +5 -14
  326. package/server/dist/modules/loops/runtime/loop-implement-recipe.js +49 -0
  327. package/server/dist/modules/loops/runtime/loop-run-manager.js +3 -3
  328. package/server/dist/modules/loops/runtime/loop-templates.js +29 -5
  329. package/server/dist/modules/loops/runtime/loops-router.js +17 -0
  330. package/server/dist/modules/missions/runtime/agent-context-resolver.js +1 -1
  331. package/server/dist/modules/missions/runtime/agent-operator-prompt.js +17 -8
  332. package/server/dist/modules/specs/runtime/spec-models.js +4 -7
  333. package/server/dist/modules/subscription-usage/adapters/claude.js +113 -0
  334. package/server/dist/modules/subscription-usage/adapters/codex.js +164 -0
  335. package/server/dist/modules/subscription-usage/adapters/errors.js +15 -0
  336. package/server/dist/modules/subscription-usage/adapters/http.js +20 -0
  337. package/server/dist/modules/subscription-usage/adapters/local-auth.js +61 -0
  338. package/server/dist/modules/subscription-usage/application.js +11 -0
  339. package/server/dist/modules/subscription-usage/domain.js +70 -0
  340. package/server/dist/modules/subscription-usage/index.js +2 -0
  341. package/server/dist/modules/subscription-usage/ports.js +2 -0
  342. package/server/dist/modules/subscription-usage/runtime/composition.js +20 -0
  343. package/server/dist/modules/subscription-usage/runtime/usage-service.js +111 -0
  344. package/server/dist/project-registry.js +16 -1
  345. package/server/dist/project-repositories.js +51 -2
  346. package/server/dist/project-router-helpers.js +1 -1
  347. package/server/dist/project-router-loop-runs.js +4 -1
  348. package/server/dist/providers/claude-adapter.js +6 -4
  349. package/server/dist/providers/codex-adapter.js +4 -5
  350. package/server/dist/schemas/workflow-definition.schema.json +2 -0
  351. package/client/dist/assets/AgentBrowserCapture-PI-cxyxi.js +0 -1
  352. package/client/dist/assets/AgentModeAnalyticsPane-mQsOqdXx.js +0 -2
  353. package/client/dist/assets/AgentModeCodePane-BL-vpmlY.js +0 -2
  354. package/client/dist/assets/CodePage-Alitorrv.js +0 -3
  355. package/client/dist/assets/DocsDialog-DwL6rgcT.js +0 -11
  356. package/client/dist/assets/DocsPage-BA1hiJZ-.js +0 -11
  357. package/client/dist/assets/InteractiveJobComposer-B3H1YHr6.js +0 -19
  358. package/client/dist/assets/JobDetailModal-XZQ9eAAy.js +0 -1
  359. package/client/dist/assets/JobDetailPage-B-SIaq_q.js +0 -1
  360. package/client/dist/assets/JobsPage-iQXAv2mx.js +0 -1
  361. package/client/dist/assets/LoopBuilderPage-B_gbFAHn.js +0 -1
  362. package/client/dist/assets/LoopsPage-BZwcY5cu.js +0 -2
  363. package/client/dist/assets/MinimizedChatsContext-CNx_0bZu.js +0 -1
  364. package/client/dist/assets/PluginsPage-DFocnoHX.js +0 -2
  365. package/client/dist/assets/ProjectSettingsDialog-Bh3S6EBB.js +0 -1
  366. package/client/dist/assets/RepositoryScopeSelector-C2mvWBQ6.js +0 -1
  367. package/client/dist/assets/ReviewPacketPage-CrAf-WDy.js +0 -4
  368. package/client/dist/assets/TemplatePreviewModal-C7DordpN.js +0 -1
  369. package/client/dist/assets/TicketDetailModalContext-B-DySpKL.js +0 -16
  370. package/client/dist/assets/agent-BaG10AN6.js +0 -1
  371. package/client/dist/assets/agent-CH4aJ884.js +0 -1
  372. package/client/dist/assets/agent-CLc9fA7v.js +0 -1
  373. package/client/dist/assets/agent-DHh9-AdL.js +0 -1
  374. package/client/dist/assets/agent-DkKnd6Wv.js +0 -1
  375. package/client/dist/assets/agent-aiVhqFW1.js +0 -1
  376. package/client/dist/assets/agent-dbznD3eO.js +0 -1
  377. package/client/dist/assets/agent-sMVFcPwL.js +0 -1
  378. package/client/dist/assets/agentRuntime-BeUb3g9g.js +0 -1
  379. package/client/dist/assets/file-text-CFT-xXKN.js +0 -1
  380. package/client/dist/assets/index-BS7KCWkV.css +0 -2
  381. package/client/dist/assets/index-C4Mv6tV6.js +0 -80
  382. package/client/dist/assets/integrations-CH0zy1Mw.js +0 -1
  383. package/client/dist/assets/integrations-CIAjhd7L.js +0 -1
  384. package/client/dist/assets/integrations-CZdwOe8A.js +0 -1
  385. package/client/dist/assets/integrations-Ct7omMld.js +0 -1
  386. package/client/dist/assets/integrations-D6MSxy3-.js +0 -1
  387. package/client/dist/assets/integrations-DbZa-Zyc.js +0 -1
  388. package/client/dist/assets/integrations-b8pH-vfl.js +0 -1
  389. package/client/dist/assets/integrations-mB-7zoyT.js +0 -1
  390. package/client/dist/assets/jira-api-BYbEPlkr.js +0 -1
  391. package/client/dist/assets/layers-eDMjG41I.js +0 -1
  392. package/client/dist/assets/loops-B4_D-nbg.js +0 -1
  393. package/client/dist/assets/nav-B-tpjyc2.js +0 -1
  394. package/client/dist/assets/nav-BJNXGg29.js +0 -1
  395. package/client/dist/assets/nav-BdhtRLP7.js +0 -1
  396. package/client/dist/assets/nav-ByYgyN5T.js +0 -1
  397. package/client/dist/assets/nav-DA0Mva2c.js +0 -1
  398. package/client/dist/assets/nav-DcJm_Jg_.js +0 -1
  399. package/client/dist/assets/nav-DhMPtAXc.js +0 -1
  400. package/client/dist/assets/nav-DhPb-Isc.js +0 -1
  401. package/client/dist/assets/provider-capabilities-Vi1ZGvAK.js +0 -1
  402. package/client/dist/assets/save-B6wGWwBR.js +0 -1
  403. package/client/dist/assets/settings-3o4Ntzi5.js +0 -1
  404. package/client/dist/assets/settings-B97jlkwD.js +0 -1
  405. package/client/dist/assets/settings-BTaT_bsS.js +0 -1
  406. package/client/dist/assets/settings-CT8w7WiC.js +0 -1
  407. package/client/dist/assets/settings-Cvr8_tvh.js +0 -1
  408. package/client/dist/assets/settings-Dn1qgAC8.js +0 -1
  409. package/client/dist/assets/settings-EShDdg4J.js +0 -1
  410. package/client/dist/assets/settings-tfLhQZRN.js +0 -1
  411. package/client/dist/assets/setup-CBMu3tqH.js +0 -1
  412. package/client/dist/assets/setup-CPh-e6J2.js +0 -1
  413. package/client/dist/assets/setup-CRKLp_ke.js +0 -1
  414. package/client/dist/assets/setup-Cy302v4J.js +0 -1
  415. package/client/dist/assets/setup-D681vTPf.js +0 -1
  416. package/client/dist/assets/setup-DYK0VBkE.js +0 -1
  417. package/client/dist/assets/setup-KAuiIiVs.js +0 -1
  418. package/client/dist/assets/setup-OGX-oCfI.js +0 -1
  419. package/client/dist/assets/useSharedWebSocket-BZMrX4jr.js +0 -2
  420. package/client/dist/assets/wrench-BfmNO9Vw.js +0 -1
  421. package/docs/agy-cli-provider-study.md +0 -257
  422. package/docs/gemini-cli-provider-study.md +0 -301
  423. package/docs/gemini-core-support-evaluation.md +0 -160
  424. package/docs/gemini.md +0 -257
  425. package/docs/kimi.md +0 -291
@@ -21,11 +21,6 @@ SpecsBoard (left) Rails (right)
21
21
 
22
22
  A **rail** is an execution lane. Drag a spec card from the SpecsBoard onto a rail and press **▶ Play** to launch the pipeline. Rails let you organise and queue work into named lanes.
23
23
 
24
- Rails default to the project's primary provider. If several are installed, a
25
- per-rail engine selector can launch on **Claude**, **Codex**, **Gemini**, or
26
- **Kimi** — see [Using Kimi](kimi.md). Implement runs on every
27
- registered provider; Freestyle is offered by Claude and Kimi.
28
-
29
24
  > **One job at a time per project.** Each project has a single queue, so within a project only one rail job runs at a time; the rest queue behind it. Real parallelism is **across projects** — open two projects and their rails run independently. See [Running multiple rails](#running-multiple-rails).
30
25
 
31
26
  ## Rails
@@ -36,8 +31,6 @@ Each rail has a header with:
36
31
  - **Spec list** — the IDs of the specs assigned to this rail. Drag in more, drag out to detach. You can also use the **Move to rail** popover from a spec card; it shows a status dot per rail so you don't push work onto a busy lane.
37
32
  - **Loop picker** — the **Loop** this rail runs: a built-in (`Implement`, `SDD Quick (OpenSpec)`, or `Freestyle`) or one of your published custom loops. See [Loops](#loops).
38
33
  - **Profile picker** — which agent profile this rail uses. This only appears once the project has **at least one** profile (create them on the Agents page). When present, `No profile` runs the rail in legacy mode.
39
- - **Engine selector** — pick which installed provider (Claude, Codex, Gemini,
40
- or Kimi) runs this rail. It renders only for multi-provider projects.
41
34
  - **Play / Stop button** — start or cancel.
42
35
 
43
36
  ### Loops
@@ -67,11 +60,6 @@ Beyond the built-ins, the **Loops** section (left sidebar, above the project lis
67
60
 
68
61
  Each run is bounded by **max iterations**, a wall-clock **timeout**, and an optional **cost cap** (USD, checked between steps). The builder also has live validation, a dry-run preview (resolve every step's exact text without spawning), import/export to JSON, and copy/paste of steps across loops. **Publish** makes a loop selectable on any rail. Built-ins are editable loops too: edit one in place and publish it to change that built-in for every rail, agent-chat launch and Companion launch that uses it (rails keep the last published version while your edit is a Draft); **Restore original** resets it and **Duplicate** copies it into a separate loop. Built-ins cannot be deleted.
69
62
 
70
- On the legacy traversal, Kimi runs built-in and custom loops whose graph contains AI/shell steps but no
71
- **Loop Decider**. A Decider's constrained `continue`/`stop` verdict is a
72
- pure-output action; Kimi 0.27 `-p` cannot enforce the required no-tools
73
- boundary, so Kimi + Decider is rejected before the loop starts.
74
-
75
63
  > Loop runs stream live in the Jobs view, grouped by scoped attempts. Core runs also expose their nested graph. Project role settings apply unless a deliberate launch override selects an engine for the run. Legacy profile routing and orchestrator settings remain available for older workflows.
76
64
 
77
65
  ### Pipeline phases
@@ -96,9 +84,6 @@ continue through the ordinary verification path.
96
84
  Architect ──► Developer ──► Reviewer ──► Ship
97
85
  ```
98
86
 
99
- Each phase is a specialised role invoked by the rail's engine (Claude, Codex,
100
- Gemini, or Kimi) in an isolated project worktree:
101
-
102
87
  | Phase | Agent | What it does |
103
88
  |-------|-------|--------------|
104
89
  | Architect | `sr-architect` | Plans the implementation |
@@ -119,17 +104,8 @@ autonomously with native tools.
119
104
 
120
105
  - **One job per spec.** If the rail has three specs, `Freestyle` launches three independent jobs.
121
106
  - **Variable cost.** Because the run is open-ended, pressing Play opens a confirmation dialog before anything spawns.
122
- - **Model picker.** A per-rail control uses the selected provider's model
123
- catalog. Kimi offers `k3`, `kimi-for-coding`, and
124
- `kimi-for-coding-highspeed`; low/high/max effort appears only for K3.
125
- - **Capability-gated.** Claude and Kimi advertise Freestyle. Codex and Gemini
107
+ - **Capability-gated.** Claude advertise Freestyle. Codex
126
108
  do not. Agent profiles do not apply to Freestyle rails.
127
- - **Transport-aware interaction.** Claude's persistent-stdin Freestyle run is
128
- a live session and waits for **Finalize**. Kimi Freestyle is agentic too,
129
- but `kimi -p` has no persistent stdin transport; it follows the owned
130
- one-process lifecycle and later conversations resume through a new process
131
- only after Kimi has emitted a session hint. Can be disabled server-side via
132
- `SPECRAILS_INTERACTIVE_JOBS=false`.
133
109
 
134
110
  You can customise the Freestyle pre-prompt per project on the [Settings page](customizing.md#freestyle-pre-prompt).
135
111
 
@@ -166,20 +142,10 @@ Two panels sit above the streaming log:
166
142
 
167
143
  Below: the full streaming log with auto-scroll, search, and copy.
168
144
 
169
- **Talk to the running job.** Every Claude job (and every Claude AI step in a loop run) is an interactive session by default: a chat composer sits at the bottom of the Job Detail page — and of the job modal in mission mode — so you can ask the running agent a question or steer it mid-run. Messages sent while the model is streaming queue up and run next; the job keeps following its plan. The composer shows a live `N turns · $X` line summed from each completed turn's real usage (still no estimates). Most jobs finish on their own the moment a turn completes with nothing queued — a subtle **Wrap up now** ends them early; Freestyle jobs instead wait for your explicit **Finalize**. During a loop run your messages reach the currently running AI step (between steps the composer shows a short waiting state, and **Settle this step** advances the loop). Codex, Gemini, and Kimi rail processes do not expose Claude's persistent-stdin transport; Kimi chat surfaces resume a recorded session by spawning a new `kimi -p` process. Server kill switch: `SPECRAILS_INTERACTIVE_JOBS=false`.
170
-
171
145
  ### Cancelling a job
172
146
 
173
147
  Click **Stop** on the rail header. The app sends `SIGTERM` to the subprocess, waits **5 s**, then `SIGKILL`.
174
148
 
175
- ### If a rail won't launch
176
-
177
- If you pick an engine whose CLI isn't installed on your machine, the launch
178
- fails fast instead of starting a broken job. Install the missing provider CLI
179
- — see [Using Codex](codex.md), [Using Gemini](gemini.md), or
180
- [Using Kimi](kimi.md) — then launch again. Kimi requires the external
181
- authenticated `kimi` 0.27+ executable; Desktop never starts a server for it.
182
-
183
149
  ### Diagnostic export
184
150
 
185
151
  Visible only when [telemetry](customizing.md#telemetry) was enabled for the job. Click **Export diagnostic** in the Job Detail header to download a ZIP containing:
@@ -219,9 +185,6 @@ Pick the profile from the **rail header's profile dropdown**. It's preselected t
219
185
 
220
186
  The **`No profile`** option always exists — use it to run a rail exactly as it did pre-4.1.0. (The dropdown itself only appears once the project has at least one profile.)
221
187
 
222
- > **Profiles apply to providers that advertise role/profile execution.** Claude
223
- > and Kimi support them. Codex and Gemini currently run in legacy mode.
224
-
225
188
  ### Custom agents (Agent Studio)
226
189
 
227
190
  From the Agents Catalog tab, the toolbar offers three creation entry points:
@@ -256,8 +219,6 @@ Per-project bundled integrations. Click **Integrations** in the project's right
256
219
 
257
220
  Each plugin tile has:
258
221
 
259
- - **AI provider** — for multi-provider projects, selects the independent
260
- Claude, Codex, or Kimi installation to inspect or change.
261
222
  - **Status** — `not installed`, `installed`, `orphan` (state file mentions it but `.mcp.json` doesn't), or `degraded` (verify failed).
262
223
  - **Preview install** — shows which `mcpServers` entries and agent fragments will land where, so you can sanity-check before clicking.
263
224
  - **Install** — applies the changes. Progress streams over the WebSocket (`plugin.install_progress` event).
@@ -281,11 +242,6 @@ Degraded plugins are **non-blocking** — the rail spawns anyway, but a `plugin.
281
242
  The app never wholesale rewrites these files:
282
243
 
283
244
  - `<project>/.mcp.json` — surgical merge per plugin.
284
- - `<project>/.kimi-code/mcp.json` — Kimi-native surgical MCP merge.
285
- - `<project>/CLAUDE.md`, `<project>/AGENTS.md`,
286
- `<project>/GEMINI.md`, or `<project>/.kimi-code/AGENTS.md` —
287
- provider-native managed instruction blocks. Provider-relative paths remain
288
- independent unless two adapters explicitly declare the same path.
289
245
  - `<project>/.specrails/plugins/state.json` — install registry.
290
246
  - `<project>/.specrails/plugins/snapshots/<jobId>.json` — per-job snapshots.
291
247
  - `<project>/.claude/agents/custom-<plugin>.md` — optional fragment per plugin.
@@ -315,5 +271,4 @@ If something looks wrong:
315
271
  - [Tracking cost](tracking-cost.md) — see what each rail run is costing you.
316
272
  - [Customising the app](customizing.md) — daily budget, per-job alerts, telemetry.
317
273
  - [Using Codex](codex.md) — run rails on the Codex CLI.
318
- - [Using Gemini](gemini.md) — run rails on the Gemini CLI.
319
274
  - [Agent profile internals](internals/profiles.md) — for power users.
@@ -0,0 +1,19 @@
1
+ # Local subscription usage
2
+
3
+ The left sidebar's **Usage** section shows the currently signed-in Claude and Codex account allowance across all activity, including use outside Specrails. Each provider window has its own consumed percentage and reset time; this is separate from job tokens, billed costs and project budgets.
4
+
5
+ The sidebar displays static provider cards with separate labeled quota bars. There is no floating usage panel or clickable provider card. Full reset times and data details are available in Subscription usage in global app settings. The view is always detailed, without view selectors or provider-management buttons. The collapsed sidebar shows a usage status icon. Unknown measurements are unavailable, never assumed to be zero.
6
+
7
+ If neither CLI is detected, the section explains installing Claude or Codex and signing in with that CLI. It does not run usage queries for missing tools. After installation or changing login, press Refresh. An API-key login may have no subscription allowance. An incompatible Codex CLI must be updated rather than queried through a paid prompt.
8
+
9
+ Updates are demand-driven while the app and a usage surface are visible. Manual refresh has a 30-second floor and provider failures can impose a longer cooldown. Data becomes stale after five minutes or a passed reset; refresh confirms the new allowance. A transient failure can retain figures from the same account with a stale label. Sign-out/auth failures clear cached figures.
10
+
11
+ Credentials remain local to the server and are never sent to the frontend. The feature does not manage accounts, change provider selection, stop jobs or store usage history. Claude OAuth/Keychain availability depends on the installed CLI and OS access policy; Codex uses read-only app-server methods. Unsupported login or platform combinations show a descriptive state. Only the current host login is supported; remote/WSL account usage is not inferred.
12
+
13
+ The Usage section starts collapsed. Its header toggles the provider cards with an accessible expanded state and chevron; the separate refresh button does not toggle the section.
14
+
15
+ Opening the Usage section requests a refresh each time, retaining cached measurements while collection runs and respecting service cooldowns. Closing it does not request a refresh.
16
+
17
+ Final placement: Usage is in the shared footer status bar in board and mission modes, removed from the left sidebar. Hover, focus or click opens the provider cards above the footer; opening requests refresh. Pointer transfer to the menu stays open, leaving dismisses after a short delay, and Escape/outside interaction closes it. Mission mode uses the minimal status bar without spend polling. This supersedes prior sidebar placement.
18
+
19
+ Footer summaries show one quota per provider: account weekly when reported, otherwise the first account window, otherwise the first reported window. Values are never aggregated. The hover menu shows every individual quota and bar. There is no presentation selector or mode label.
package/docs/terminal.md CHANGED
@@ -32,7 +32,6 @@ You don't have to. You can keep using iTerm/Windows Terminal/Alacritty/whatever
32
32
 
33
33
  The panel header carries three one-click shortcuts:
34
34
 
35
- - **Open AI CLI** (✨ Sparkles) — spins up a fresh session and types the launch command for you. On a single-provider project it launches the project's provider CLI directly (`claude`, `codex`, `gemini`, or `kimi`); on a multi-provider project it opens a small picker so you choose which CLI to start. Kimi launches the CLI itself, not `kimi server`.
36
35
  - **Open in browser** (🌐 Globe) — opens the URL from the `browserShortcutUrl` setting (default `https://specrails.dev`). Right-click it to jump to the setting and change the URL.
37
36
  - **Paste quick script** (`</>`) — writes the snippet from the `quickScript` setting (which defaults to a personalised `echo "Wake up, <username> …"` reminder, editable in Settings) into the active session. Right-click it to edit the snippet in Settings. Disabled when there's no active terminal or the snippet is empty.
38
37
 
@@ -141,4 +140,3 @@ For deeper inspection of how the panel resolves PATH at startup (relevant for Vo
141
140
 
142
141
  - [Customising the app](customizing.md) — terminal settings, themes.
143
142
  - [Getting started](getting-started.md) — registering a project.
144
- - [Codex](codex.md) / [Gemini](gemini.md) — setting up the CLI behind the **Open AI CLI** button on a Codex or Gemini project.
@@ -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.0",
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.
@@ -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)