specrails-desktop 2.46.0 → 2.48.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 (259) hide show
  1. package/README.md +2 -0
  2. package/client/dist/assets/{ActivityFeedPage-CsHQPpvl.js → ActivityFeedPage-CGq6QhP5.js} +1 -1
  3. package/client/dist/assets/AgentBrowserCapture-C4GZ-kdf.js +1 -0
  4. package/client/dist/assets/AgentModeAnalyticsPane-_IPmL0ZS.js +2 -0
  5. package/client/dist/assets/AgentModeCodePane-B5WG5DOP.js +2 -0
  6. package/client/dist/assets/{AgentModeJobsPane-rnD-EPMd.js → AgentModeJobsPane-w1N_HIXh.js} +1 -1
  7. package/client/dist/assets/AgentsPage-CmwnnT46.js +87 -0
  8. package/client/dist/assets/AnalyticsPage-BUMsKtsJ.js +1 -0
  9. package/client/dist/assets/CodePage-PjgywJPS.js +3 -0
  10. package/client/dist/assets/DesktopAnalyticsPage-BXY1pH4y.js +1 -0
  11. package/client/dist/assets/DocsDialog-ccQETHKB.js +11 -0
  12. package/client/dist/assets/DocsPage-DzEgplB3.js +11 -0
  13. package/client/dist/assets/{ExportDropdown-pWZGYCv3.js → ExportDropdown-B8bBGopr.js} +1 -1
  14. package/client/dist/assets/InteractiveJobComposer-CEwGkJ8F.js +19 -0
  15. package/client/dist/assets/JobDetailModal-BYXmuTDe.js +1 -0
  16. package/client/dist/assets/JobDetailPage-CWp6MwJj.js +1 -0
  17. package/client/dist/assets/JobsPage-D1O1uyG8.js +1 -0
  18. package/client/dist/assets/{LoopBuilderPage-CtmOE4-b.js → LoopBuilderPage-DQE-7Vby.js} +2 -2
  19. package/client/dist/assets/{LoopPreviewModal-BYeLjQbr.js → LoopPreviewModal-Bk7zIXD5.js} +1 -1
  20. package/client/dist/assets/LoopsPage-CqG7mx56.js +1 -0
  21. package/client/dist/assets/MinimizedChatsContext-UpnqccPx.js +1 -0
  22. package/client/dist/assets/PluginsPage-CjgVvmpW.js +2 -0
  23. package/client/dist/assets/{ProjectSettingsDialog-DsCRIc4v.js → ProjectSettingsDialog-D6vBCoay.js} +1 -1
  24. package/client/dist/assets/{RepositoryDeliveries-BQm_3TO8.js → RepositoryDeliveries-C36ipKp_.js} +1 -1
  25. package/client/dist/assets/{RepositoryScopeSelector-DnAFtjTz.js → RepositoryScopeSelector-0Gz_fR1E.js} +1 -1
  26. package/client/dist/assets/ReviewPacketPage-Dr9RYs7q.js +1 -0
  27. package/client/dist/assets/TemplatePreviewModal-sCMHYFJo.js +1 -0
  28. package/client/dist/assets/TicketDetailModalContext-C5GgJlEt.js +14 -0
  29. package/client/dist/assets/{Trans-BwLgX6n7.js → Trans-9pHTy2fH.js} +1 -1
  30. package/client/dist/assets/{addspec-D2YIQ0GU.js → addspec-BOvgmcgk.js} +1 -1
  31. package/client/dist/assets/{addspec-B_TPJt-R.js → addspec-Clmyfn_R.js} +1 -1
  32. package/client/dist/assets/{addspec-A1lDQa4M.js → addspec-CsNaBpHp.js} +1 -1
  33. package/client/dist/assets/{addspec-CQBjzt24.js → addspec-D2tvWmmV.js} +1 -1
  34. package/client/dist/assets/{addspec-CLYLvaVP.js → addspec-D3nFwvD0.js} +1 -1
  35. package/client/dist/assets/{addspec-BPpZ_olp.js → addspec-DVLEhK3w.js} +1 -1
  36. package/client/dist/assets/{addspec-iFdQ6D99.js → addspec-DXbK-tb0.js} +1 -1
  37. package/client/dist/assets/{addspec-BHZd0N5F.js → addspec-OHG8DFEn.js} +1 -1
  38. package/client/dist/assets/agentRuntime-BUimiX__.js +1 -0
  39. package/client/dist/assets/agentRuntime-BcN97sAB.js +1 -0
  40. package/client/dist/assets/agentRuntime-Ckyg-ahT.js +1 -0
  41. package/client/dist/assets/agentRuntime-DJCwyX4t.js +1 -0
  42. package/client/dist/assets/agentRuntime-DVzPJOl0.js +1 -0
  43. package/client/dist/assets/agentRuntime-V9AZTG3c.js +1 -0
  44. package/client/dist/assets/agentRuntime-bAAjw1vw.js +1 -0
  45. package/client/dist/assets/agentRuntime-v4qKAYH-.js +1 -0
  46. package/client/dist/assets/{agents-45GbINfh.js → agents-CA5eT1d0.js} +1 -1
  47. package/client/dist/assets/{agents-DMKFStHL.js → agents-CAgy-5uS.js} +1 -1
  48. package/client/dist/assets/{agents-IUOgqT5t.js → agents-CNm6Qzlv.js} +1 -1
  49. package/client/dist/assets/{agents-ms7TwG16.js → agents-CTwbx-fb.js} +1 -1
  50. package/client/dist/assets/{agents-DaYcxAr1.js → agents-DEGV1Vao.js} +1 -1
  51. package/client/dist/assets/{agents-BHp0_FQt.js → agents-HsDF1aQU.js} +1 -1
  52. package/client/dist/assets/{agents-Bvyi3y6R.js → agents-IQjdA-z9.js} +1 -1
  53. package/client/dist/assets/{agents-rzPnqoBR.js → agents-RkGoteXh.js} +1 -1
  54. package/client/dist/assets/analytics-BNgZ1Nj7.js +1 -0
  55. package/client/dist/assets/analytics-BlypLLR6.js +1 -0
  56. package/client/dist/assets/analytics-CoEzWPx4.js +1 -0
  57. package/client/dist/assets/analytics-DFh5lSOl.js +1 -0
  58. package/client/dist/assets/analytics-DZp-DS2U.js +1 -0
  59. package/client/dist/assets/analytics-DjLlE7jS.js +1 -0
  60. package/client/dist/assets/analytics-jzCkOec3.js +1 -0
  61. package/client/dist/assets/analytics-nZIjjtCc.js +1 -0
  62. package/client/dist/assets/{dashboard-BK_mez-z.js → dashboard-C29Qwa-L.js} +1 -1
  63. package/client/dist/assets/{dashboard-BwkjxI6H.js → dashboard-CSJzf5OT.js} +1 -1
  64. package/client/dist/assets/{dashboard-C3bDKf7s.js → dashboard-CTfEtkqX.js} +1 -1
  65. package/client/dist/assets/{dashboard-Bt0cCzeG.js → dashboard-Cr4D8oMn.js} +1 -1
  66. package/client/dist/assets/{dashboard-fRrP0Kv0.js → dashboard-Csf1yGSN.js} +1 -1
  67. package/client/dist/assets/{dashboard-CdvjGfog.js → dashboard-D40HP2ks.js} +1 -1
  68. package/client/dist/assets/dashboard-DBAP2_SQ.js +1 -0
  69. package/client/dist/assets/{dashboard-B14uYXkG.js → dashboard-DMklepmz.js} +1 -1
  70. package/client/dist/assets/file-text-CFT-xXKN.js +1 -0
  71. package/client/dist/assets/{formatDistanceToNow-CeZv93iR.js → formatDistanceToNow-w_2U5pm0.js} +1 -1
  72. package/client/dist/assets/{getTimezoneOffsetInMilliseconds-DxAkjvJb.js → getTimezoneOffsetInMilliseconds-BRymg1Kk.js} +1 -1
  73. package/client/dist/assets/index-D0ScFZvR.css +2 -0
  74. package/client/dist/assets/index-DORCrgbI.js +77 -0
  75. package/client/dist/assets/jira-api-BqHbSdxK.js +1 -0
  76. package/client/dist/assets/jobs-C6qmvUAi.js +1 -0
  77. package/client/dist/assets/jobs-C9j2PuwW.js +1 -0
  78. package/client/dist/assets/jobs-DLHdDkHc.js +1 -0
  79. package/client/dist/assets/jobs-DSp9DoD_.js +1 -0
  80. package/client/dist/assets/jobs-DTQO08nJ.js +1 -0
  81. package/client/dist/assets/jobs-DoFaEl9g.js +1 -0
  82. package/client/dist/assets/jobs-Dpw6AEOP.js +1 -0
  83. package/client/dist/assets/jobs-DrF8Do8s.js +1 -0
  84. package/client/dist/assets/{narration-BSldQfjd.js → narration--hUhjM5v.js} +1 -1
  85. package/client/dist/assets/{narration-DKLjXIFI.js → narration-CHI06czm.js} +1 -1
  86. package/client/dist/assets/{narration-4OSg0b5-.js → narration-DjKB3pQZ.js} +1 -1
  87. package/client/dist/assets/{narration-BPMOQPFe.js → narration-DtCYvRLM.js} +1 -1
  88. package/client/dist/assets/{narration-D-e9ssxA.js → narration-DvLQBW9U.js} +1 -1
  89. package/client/dist/assets/{narration-DnrKC3mF.js → narration-eSx3gR0W.js} +1 -1
  90. package/client/dist/assets/{narration-Dypjxeel.js → narration-n3hOA7LY.js} +1 -1
  91. package/client/dist/assets/{narration-CvQdWdR5.js → narration-thJOeT2-.js} +1 -1
  92. package/client/dist/assets/packet-BNW8ODQw.js +1 -0
  93. package/client/dist/assets/packet-CXYvfdjy.js +1 -0
  94. package/client/dist/assets/packet-CtIlB8YE.js +1 -0
  95. package/client/dist/assets/packet-DA1SJkMH.js +1 -0
  96. package/client/dist/assets/packet-DYcWfBip.js +1 -0
  97. package/client/dist/assets/packet-DseX9yKC.js +1 -0
  98. package/client/dist/assets/packet-_m5aOy_d.js +1 -0
  99. package/client/dist/assets/packet-qOemUAm3.js +1 -0
  100. package/client/dist/assets/{project-repositories-BvBj9XH9.js → project-repositories-BsCs1PPA.js} +1 -1
  101. package/client/dist/assets/provider-capabilities-Vi1ZGvAK.js +1 -0
  102. package/client/dist/assets/settings-BsehKpty.js +1 -0
  103. package/client/dist/assets/settings-CQWob3SB.js +1 -0
  104. package/client/dist/assets/settings-CgCl0hzy.js +1 -0
  105. package/client/dist/assets/settings-DLRLd22L.js +1 -0
  106. package/client/dist/assets/settings-Dn5prAFl.js +1 -0
  107. package/client/dist/assets/settings-HZSu-K4l.js +1 -0
  108. package/client/dist/assets/{settings-DEgcA0jd.js → settings-p4OVIyRh.js} +1 -1
  109. package/client/dist/assets/settings-rddaldUp.js +1 -0
  110. package/client/dist/assets/setup-B5FHuxvB.js +1 -0
  111. package/client/dist/assets/setup-BihzXadx.js +1 -0
  112. package/client/dist/assets/setup-BtC6Hg4K.js +1 -0
  113. package/client/dist/assets/setup-C9Fy9PKQ.js +1 -0
  114. package/client/dist/assets/setup-CROyqD5N.js +1 -0
  115. package/client/dist/assets/setup-DpqzaEEA.js +1 -0
  116. package/client/dist/assets/setup-Dwzt5nO1.js +1 -0
  117. package/client/dist/assets/setup-zLEDnG7N.js +1 -0
  118. package/client/dist/assets/{spending-DNM6NAoA.js → spending-hcgxHP4O.js} +1 -1
  119. package/client/dist/assets/{useDesktop-mxHVQOdx.js → useDesktop-CAisjKWC.js} +1 -1
  120. package/client/dist/assets/{useSharedWebSocket-DnxHj8jP.js → useSharedWebSocket-BHIfW4QT.js} +2 -2
  121. package/client/dist/assets/wrench-BfmNO9Vw.js +1 -0
  122. package/client/dist/index.html +27 -26
  123. package/docs/guide/de/integrations/7-local-engines.md +37 -0
  124. package/docs/guide/en/integrations/7-local-engines.md +37 -0
  125. package/docs/guide/es/integrations/7-local-engines.md +37 -0
  126. package/docs/guide/fr/integrations/7-local-engines.md +37 -0
  127. package/docs/guide/it/integrations/7-local-engines.md +37 -0
  128. package/docs/guide/ja/integrations/7-local-engines.md +37 -0
  129. package/docs/guide/pt/integrations/7-local-engines.md +37 -0
  130. package/docs/guide/zh/integrations/7-local-engines.md +37 -0
  131. package/docs/internals/adding-a-provider.md +12 -0
  132. package/docs/internals/local-agent-runner.md +153 -0
  133. package/docs/internals/review-packet.md +8 -0
  134. package/docs/local-providers.md +215 -0
  135. package/docs/plans/implementation-efficiency-artifact.json +18 -0
  136. package/docs/plans/implementation-efficiency-evaluation.json +1817 -0
  137. package/docs/plans/implementation-efficiency-evaluation.md +41 -0
  138. package/docs/plans/implementation-efficiency-progress.md +48 -0
  139. package/docs/plans/implementation-efficiency-release.md +41 -0
  140. package/docs/plans/implementation-efficiency.md +124 -0
  141. package/local-runner/dist/specrails-local-runner.js +15815 -0
  142. package/package.json +7 -5
  143. package/server/dist/agent-chat-manager.js +8 -1
  144. package/server/dist/agent-chat-router.js +5 -2
  145. package/server/dist/agent-fence-promotion.js +53 -0
  146. package/server/dist/agent-mcp-config.js +4 -1
  147. package/server/dist/agent-runtime-bridge.js +35 -26
  148. package/server/dist/agent-runtime-controls-router.js +20 -1
  149. package/server/dist/agent-runtime-controls.js +94 -12
  150. package/server/dist/agent-runtime-effective-config.js +53 -0
  151. package/server/dist/agent-runtime-events.js +46 -0
  152. package/server/dist/agent-runtime-history.js +47 -0
  153. package/server/dist/agent-runtime-loader.js +84 -5
  154. package/server/dist/agent-runtime-metrics.js +77 -1
  155. package/server/dist/agent-runtime-package.js +205 -0
  156. package/server/dist/agent-runtime-settings-router.js +96 -5
  157. package/server/dist/agent-runtime-settings.js +163 -7
  158. package/server/dist/agent-spec-framing.js +10 -4
  159. package/server/dist/binary-probe.js +12 -0
  160. package/server/dist/blueprint-router.js +3 -2
  161. package/server/dist/chat-manager.js +5 -1
  162. package/server/dist/desktop-router.js +87 -4
  163. package/server/dist/index.js +13 -0
  164. package/server/dist/interactive-job-session.js +3 -1
  165. package/server/dist/local-engine-detection.js +118 -0
  166. package/server/dist/loop-executors.js +4 -3
  167. package/server/dist/loop-role-engines.js +129 -0
  168. package/server/dist/loop-run-manager.js +14 -6
  169. package/server/dist/mcp/tools/app-settings.js +30 -4
  170. package/server/dist/mcp/tools/rails.js +2 -1
  171. package/server/dist/pr-body.js +11 -2
  172. package/server/dist/pricing.js +15 -0
  173. package/server/dist/provider-detection.js +63 -1
  174. package/server/dist/provider-selection.js +26 -1
  175. package/server/dist/providers/index.js +4 -1
  176. package/server/dist/providers/local-adapter-registry.js +56 -0
  177. package/server/dist/providers/local-adapter.js +426 -0
  178. package/server/dist/providers/registry.js +39 -4
  179. package/server/dist/rail-isolated-launch.js +4 -3
  180. package/server/dist/rails-router.js +90 -8
  181. package/server/dist/result-event.js +17 -0
  182. package/server/dist/review-packet.js +1 -0
  183. package/server/dist/schemas/agent-runtime.schema.json +28 -3
  184. package/server/dist/schemas/fixtures/runtime-efficiency-summary.v1.json +790 -0
  185. package/server/dist/setup-manager.js +5 -1
  186. package/server/dist/spec-models.js +14 -2
  187. package/server/dist/worktree-node-modules.js +97 -65
  188. package/client/dist/assets/AgentBrowserCapture-BVcCK7x0.js +0 -1
  189. package/client/dist/assets/AgentModeAnalyticsPane-C5F3m29G.js +0 -2
  190. package/client/dist/assets/AgentModeCodePane-C5QuKsu0.js +0 -2
  191. package/client/dist/assets/AgentsPage-DiyJWZcU.js +0 -87
  192. package/client/dist/assets/AnalyticsPage-Dkmb0dnd.js +0 -1
  193. package/client/dist/assets/CodePage-C0dH99-t.js +0 -3
  194. package/client/dist/assets/DesktopAnalyticsPage-DuTjr2n8.js +0 -1
  195. package/client/dist/assets/DocsDialog-BVZJZHVR.js +0 -11
  196. package/client/dist/assets/DocsPage-D6KHTv8V.js +0 -11
  197. package/client/dist/assets/InteractiveJobComposer-Ck3PQxjN.js +0 -19
  198. package/client/dist/assets/JobDetailModal-DkoMH3ie.js +0 -1
  199. package/client/dist/assets/JobDetailPage-CBVivcl2.js +0 -1
  200. package/client/dist/assets/JobsPage-CzxEeca9.js +0 -1
  201. package/client/dist/assets/LoopsPage-6iW7g25k.js +0 -1
  202. package/client/dist/assets/MinimizedChatsContext-B6wVevPq.js +0 -1
  203. package/client/dist/assets/PluginsPage-Bf1z3WPF.js +0 -2
  204. package/client/dist/assets/ReviewPacketPage-CaXAwtR7.js +0 -1
  205. package/client/dist/assets/TemplatePreviewModal-9aRFpJ_J.js +0 -1
  206. package/client/dist/assets/TicketDetailModalContext-B1X5glzV.js +0 -14
  207. package/client/dist/assets/agentRuntime-BUagD2I_.js +0 -1
  208. package/client/dist/assets/agentRuntime-BkL70WtX.js +0 -1
  209. package/client/dist/assets/agentRuntime-CWXVqgqE.js +0 -1
  210. package/client/dist/assets/agentRuntime-CvNBdUAe.js +0 -1
  211. package/client/dist/assets/agentRuntime-D4RmeuNY.js +0 -1
  212. package/client/dist/assets/agentRuntime-DTidBWKg.js +0 -1
  213. package/client/dist/assets/agentRuntime-DhKYfyaU.js +0 -1
  214. package/client/dist/assets/agentRuntime-LL8_GLx9.js +0 -1
  215. package/client/dist/assets/analytics-47pYygtV.js +0 -1
  216. package/client/dist/assets/analytics-B0vt1g5U.js +0 -1
  217. package/client/dist/assets/analytics-BMzCvrCU.js +0 -1
  218. package/client/dist/assets/analytics-BlFN3Zwx.js +0 -1
  219. package/client/dist/assets/analytics-CZYzL-nR.js +0 -1
  220. package/client/dist/assets/analytics-DAsb9MJ8.js +0 -1
  221. package/client/dist/assets/analytics-FXQlQ2Yt.js +0 -1
  222. package/client/dist/assets/analytics-RnFCNkah.js +0 -1
  223. package/client/dist/assets/dashboard-BfBir_6o.js +0 -1
  224. package/client/dist/assets/file-text-BcLCfcWZ.js +0 -1
  225. package/client/dist/assets/index-Cfh9aaep.css +0 -2
  226. package/client/dist/assets/index-DYoyDWim.js +0 -76
  227. package/client/dist/assets/jira-api-ChjVGJx_.js +0 -1
  228. package/client/dist/assets/jobs-5PQJbdOf.js +0 -1
  229. package/client/dist/assets/jobs-B2B08Oy9.js +0 -1
  230. package/client/dist/assets/jobs-BJZtBbwb.js +0 -1
  231. package/client/dist/assets/jobs-BlWEsQb7.js +0 -1
  232. package/client/dist/assets/jobs-ConfD8xD.js +0 -1
  233. package/client/dist/assets/jobs-DMPU64ms.js +0 -1
  234. package/client/dist/assets/jobs-DojASiTp.js +0 -1
  235. package/client/dist/assets/jobs-kVHkNpB-.js +0 -1
  236. package/client/dist/assets/packet-BQ2tXfJI.js +0 -1
  237. package/client/dist/assets/packet-BdpOJ1Cy.js +0 -1
  238. package/client/dist/assets/packet-C3Ievb9R.js +0 -1
  239. package/client/dist/assets/packet-C4zJIG0Y.js +0 -1
  240. package/client/dist/assets/packet-Cx-zRuWn.js +0 -1
  241. package/client/dist/assets/packet-DUTGX52M.js +0 -1
  242. package/client/dist/assets/packet-DhsKtUPB.js +0 -1
  243. package/client/dist/assets/packet-z3LJ8Zyy.js +0 -1
  244. package/client/dist/assets/provider-capabilities-CXipwrXJ.js +0 -1
  245. package/client/dist/assets/settings-B3rWEwOx.js +0 -1
  246. package/client/dist/assets/settings-BcVoiMyw.js +0 -1
  247. package/client/dist/assets/settings-BlYvaQ7w.js +0 -1
  248. package/client/dist/assets/settings-CD5uEBiL.js +0 -1
  249. package/client/dist/assets/settings-CyhS0DLJ.js +0 -1
  250. package/client/dist/assets/settings-DCMFUGDs.js +0 -1
  251. package/client/dist/assets/settings-DmbYKyMJ.js +0 -1
  252. package/client/dist/assets/setup-2xGgu4Sk.js +0 -1
  253. package/client/dist/assets/setup-Ba11l2JA.js +0 -1
  254. package/client/dist/assets/setup-BzW4kMok.js +0 -1
  255. package/client/dist/assets/setup-COzwYhkJ.js +0 -1
  256. package/client/dist/assets/setup-CRVULhtF.js +0 -1
  257. package/client/dist/assets/setup-DgvlxwpE.js +0 -1
  258. package/client/dist/assets/setup-Dw_wYeEY.js +0 -1
  259. package/client/dist/assets/setup-ZPtEeJPW.js +0 -1
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.prDeliveryRevisionAllowed = prDeliveryRevisionAllowed;
4
4
  exports.createRailsRouter = createRailsRouter;
5
+ const agent_runtime_settings_1 = require("./agent-runtime-settings");
5
6
  const express_1 = require("express");
6
7
  const rails_store_1 = require("./rails-store");
7
8
  const queue_manager_1 = require("./queue-manager");
@@ -11,7 +12,9 @@ const file_provenance_1 = require("./file-provenance");
11
12
  const file_story_1 = require("./file-story");
12
13
  const loops_store_1 = require("./loops-store");
13
14
  const loop_runs_store_1 = require("./loop-runs-store");
15
+ const registry_1 = require("./providers/registry");
14
16
  const providers_1 = require("./providers");
17
+ const loop_role_engines_1 = require("./loop-role-engines");
15
18
  const runtime_1 = require("./providers/runtime");
16
19
  const agent_defaults_1 = require("./agent-defaults");
17
20
  const profile_manager_1 = require("./profile-manager");
@@ -373,8 +376,9 @@ function createRailsRouter() {
373
376
  }
374
377
  const value = body.aiEngine;
375
378
  const c = ctx(req);
376
- // null clears the override; a string must be one of the project's providers.
377
- if (value !== null) {
379
+ // null clears the override; a string must be one of the project's providers
380
+ // — or the `roles` sentinel (per-role engines from Settings ▸ Specrails Agents).
381
+ if (value !== null && value !== loop_role_engines_1.ROLES_ENGINE) {
378
382
  const check = (0, provider_selection_1.validateRequestedProvider)(c.project, value);
379
383
  if (!check.ok) {
380
384
  res.status(400).json({ error: check.error });
@@ -432,6 +436,14 @@ function createRailsRouter() {
432
436
  res.status(400).json({ error: 'Invalid rail index' });
433
437
  return;
434
438
  }
439
+ let runtimeProviderOverride;
440
+ try {
441
+ runtimeProviderOverride = (0, agent_runtime_settings_1.validateRuntimeProviderOverride)(req.body?.runtimeProviderOverride);
442
+ }
443
+ catch {
444
+ res.status(400).json({ error: 'invalid_runtime_provider_override' });
445
+ return;
446
+ }
435
447
  let { mode = 'implement' } = req.body ?? {};
436
448
  const { repositoryIds: rawRepositoryIds, baseDeliveryIds, profileName, aiEngine, model, loopId: rawLoopId, reasoning_effort, originConversationId, originSurface, targetPrNumber, revisionOfDeliveryId, revisionNote, baseBranch: rawBaseBranch } = req.body ?? {};
437
449
  // Revision launch (nontech-review-experience Wave 3): the user asked for a
@@ -626,7 +638,21 @@ function createRailsRouter() {
626
638
  }
627
639
  // AI engine precedence: explicit body param > stored rail engine > primary.
628
640
  // `undefined`/empty in both means "run on the project's primary provider".
629
- const requestedEngine = aiEngine === undefined ? (rail.aiEngine ?? undefined) : aiEngine;
641
+ const requestedEngineRaw = aiEngine === undefined ? (rail.aiEngine ?? undefined) : aiEngine;
642
+ // `roles` (hybrid-role-engines): the pipeline roles keep the project's
643
+ // per-role runtime settings (no flattening override) and the loop's own
644
+ // steps come from the stored loop-role engines — verifier for every
645
+ // non-core ai-step, decider for the Loop Decider. The rail's "engine" for
646
+ // the non-core path is therefore the verifier's provider.
647
+ const rolesMode = requestedEngineRaw === loop_role_engines_1.ROLES_ENGINE;
648
+ if (rolesMode && mode === 'freestyle') {
649
+ res.status(400).json({ error: 'roles_engine_unsupported_mode', detail: 'Freestyle has no roles; pick a concrete engine' });
650
+ return;
651
+ }
652
+ const loopRoleEngines = rolesMode ? (0, loop_role_engines_1.loadLoopRoleEngines)(c.project) : {};
653
+ const requestedEngine = rolesMode
654
+ ? (loopRoleEngines.verifier?.provider && (0, provider_selection_1.validateRequestedProvider)(c.project, loopRoleEngines.verifier.provider).ok ? loopRoleEngines.verifier.provider : undefined)
655
+ : requestedEngineRaw;
630
656
  const engineCheck = (0, provider_selection_1.validateRequestedProvider)(c.project, requestedEngine);
631
657
  if (!engineCheck.ok) {
632
658
  res.status(400).json({ error: engineCheck.error });
@@ -635,6 +661,37 @@ function createRailsRouter() {
635
661
  // Only pass a provider override when one was actually requested (keeps
636
662
  // single-provider rails on the legacy code path).
637
663
  const railProvider = requestedEngine ? engineCheck.provider : undefined;
664
+ // Mission/MCP and Launch-all submit aiEngine or use the saved rail engine.
665
+ // Resolve this at the shared boundary, not only in the Dashboard payload.
666
+ // Local (OpenAI-compatible) engines: the override carries provider + model
667
+ // only — OpenAI endpoints have no effort knob, so effort is DROPPED (never
668
+ // forwarded to Core's executor), and cost caps are refused (a local run has
669
+ // no billable cost to cap against).
670
+ const localEngine = (0, registry_1.isLocalAdapterId)(engineCheck.provider);
671
+ if (rolesMode && runtimeProviderOverride) {
672
+ res.status(400).json({ error: 'runtime_provider_mismatch', detail: 'A roles launch carries no provider override' });
673
+ return;
674
+ }
675
+ if (!runtimeProviderOverride && requestedEngine && !rolesMode) {
676
+ try {
677
+ runtimeProviderOverride = (0, agent_runtime_settings_1.validateRuntimeProviderOverride)({ provider: engineCheck.provider, ...(model ? { model } : {}), ...(reasoning_effort && !localEngine ? { effort: reasoning_effort } : {}) });
678
+ }
679
+ catch {
680
+ res.status(400).json({ error: 'invalid_runtime_provider_override' });
681
+ return;
682
+ }
683
+ }
684
+ if (runtimeProviderOverride && runtimeProviderOverride.provider !== engineCheck.provider) {
685
+ res.status(400).json({ error: 'runtime_provider_mismatch' });
686
+ return;
687
+ }
688
+ if (localEngine && runtimeProviderOverride?.effort !== undefined) {
689
+ runtimeProviderOverride = { provider: runtimeProviderOverride.provider, ...(runtimeProviderOverride.model !== undefined ? { model: runtimeProviderOverride.model } : {}) };
690
+ }
691
+ if (localEngine && req.body?.maxCostUsd !== undefined && req.body?.maxCostUsd !== null) {
692
+ res.status(400).json({ error: 'local_engine_no_cost_cap' });
693
+ return;
694
+ }
638
695
  // Freestyle bypasses the OpenSpec pipeline and hands the raw spec to a
639
696
  // provider that explicitly advertises autonomous freestyle support.
640
697
  if (mode === 'freestyle' && (0, providers_1.getAdapter)(engineCheck.provider).capabilities.freestyle !== true) {
@@ -755,26 +812,40 @@ function createRailsRouter() {
755
812
  return;
756
813
  }
757
814
  }
815
+ const verifierEngine = rolesMode ? (0, loop_role_engines_1.resolveLoopRoleEngine)(c.project, 'verifier', { provider: loopProvider, model: loopAdapter.defaultModel() }, loopRoleEngines) : undefined;
758
816
  const loopModel = typeof model === 'string' && model
759
817
  ? model
760
- : (profileModel ?? globalAgentDefaults?.pipelineModel ?? loopAdapter.defaultModel());
818
+ : (verifierEngine?.model ?? profileModel ?? globalAgentDefaults?.pipelineModel ?? loopAdapter.defaultModel());
761
819
  let effort;
762
- if (reasoning_effort !== undefined && reasoning_effort !== null) {
820
+ // Local (OpenAI-compatible) engines have no per-invocation effort knob
821
+ // unless the connection opts in; a requested effort is DROPPED, not a
822
+ // 400 — the engine selectors send the last-used effort blindly.
823
+ const effortRequested = localEngine && !loopAdapter.capabilities.supportsReasoningEffort ? undefined : (reasoning_effort ?? verifierEngine?.effort);
824
+ if (effortRequested !== undefined && effortRequested !== null) {
763
825
  const allowed = (0, providers_1.reasoningEffortsForModel)(loopAdapter, loopModel);
764
- if (typeof reasoning_effort !== 'string' ||
765
- !allowed.includes(reasoning_effort)) {
826
+ if (typeof effortRequested !== 'string' ||
827
+ !allowed.includes(effortRequested)) {
766
828
  res.status(400).json({
767
829
  error: `reasoning_effort is not valid for provider "${loopProvider}" and model "${loopModel}"`,
768
830
  allowed,
769
831
  });
770
832
  return;
771
833
  }
772
- effort = reasoning_effort;
834
+ effort = effortRequested;
773
835
  }
774
836
  else if (globalAgentDefaults?.pipelineEffort
775
837
  && (0, runtime_1.isReasoningEffortValidForModel)(loopAdapter, loopModel, globalAgentDefaults.pipelineEffort)) {
776
838
  effort = globalAgentDefaults.pipelineEffort;
777
839
  }
840
+ // Loop Decider engine under roles mode (else the rail's engine, as before).
841
+ // Its provider must enforce a read-only tool policy like the rail's.
842
+ const deciderEngine = rolesMode && loopGraph.nodes.some((node) => node.type === 'decider')
843
+ ? (0, loop_role_engines_1.resolveLoopRoleEngine)(c.project, 'decider', { provider: loopProvider, model: loopModel, ...(effort ? { effort } : {}) }, loopRoleEngines)
844
+ : undefined;
845
+ if (deciderEngine && !(0, providers_1.supportsToolPolicy)((0, providers_1.getAdapter)(deciderEngine.provider), 'read-only')) {
846
+ res.status(409).json({ code: 'provider_tool_policy_unsupported', provider: deciderEngine.provider, requiredPolicy: 'read-only', error: `Provider '${deciderEngine.provider}' cannot run Loop Deciders because its headless CLI does not enforce a read-only tool policy.` });
847
+ return;
848
+ }
778
849
  // The loop's command(s) declare ticket scope + required capabilities.
779
850
  const promptsText = loopGraph.nodes
780
851
  .filter((n) => n.type === 'ai-step')
@@ -907,8 +978,10 @@ function createRailsRouter() {
907
978
  }
908
979
  try {
909
980
  const ids = await (0, rail_isolated_launch_1.launchIsolatedRail)({
981
+ runtimeProviderOverride,
910
982
  ctx: c, railIndex, ticketIds: [...rail.ticketIds], repositoryIds, ...repositoryBases, loopId, loopName, loopGraph,
911
983
  provider: loopProvider, model: loopModel, effort, scope,
984
+ ...(deciderEngine ? { deciderEngine } : {}),
912
985
  profileName: resolvedProfile,
913
986
  originSurface: originSurface ?? 'dashboard',
914
987
  originConversationId: originConversationId ?? null,
@@ -1015,6 +1088,7 @@ function createRailsRouter() {
1015
1088
  };
1016
1089
  const runPromise = c.loopRunManager
1017
1090
  .run({
1091
+ runtimeProviderOverride,
1018
1092
  runId,
1019
1093
  loopId,
1020
1094
  loopName,
@@ -1040,6 +1114,7 @@ function createRailsRouter() {
1040
1114
  provider: loopProvider,
1041
1115
  model: loopModel,
1042
1116
  effort,
1117
+ ...(deciderEngine ? { deciderEngine } : {}),
1043
1118
  profileName: resolvedProfile,
1044
1119
  })
1045
1120
  .then((r) => {
@@ -1218,6 +1293,13 @@ function createRailsRouter() {
1218
1293
  catch (err) {
1219
1294
  console.warn(`[rails-router] stop: loop cancel(${runId}) failed: ${err.message}`);
1220
1295
  }
1296
+ // A run the engine no longer owns (reconciled as an orphan by a context
1297
+ // reload while still alive) never settles through onLoopRunFinished, so
1298
+ // its rail entry would pin the tickets "in flight" forever. When the
1299
+ // durable row is already terminal, release the entry here.
1300
+ const row = (0, loop_runs_store_1.getLoopRun)(c.db, runId);
1301
+ if (!row || row.status === 'completed')
1302
+ c.railLoopRuns.delete(runId);
1221
1303
  const stopMsg = {
1222
1304
  type: 'loop.run_stopped',
1223
1305
  projectId: c.project.id,
@@ -23,6 +23,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
23
23
  exports.finaliseInvocationResult = finaliseInvocationResult;
24
24
  exports.normaliseResultEvent = normaliseResultEvent;
25
25
  const pricing_1 = require("./pricing");
26
+ const local_adapter_1 = require("./providers/local-adapter");
26
27
  /**
27
28
  * Finalise an AI invocation by walking the adapter's parsed events, stamping
28
29
  * the model when the adapter does not report one, and applying the local
@@ -68,6 +69,22 @@ function finaliseInvocationResult(adapter, events, opts = {}) {
68
69
  // (COST-ACCOUNTING-AUDIT CRIT-1). A genuine native cost — including a
69
70
  // numeric 0 — still passes through untouched with estimated=false.
70
71
  const nativeCostAbsent = cloned.total_cost_usd === null || cloned.total_cost_usd === undefined;
72
+ // Local (OpenAI-compatible) engines are NEVER rate-card guessed: cost is the
73
+ // connection's optional user-supplied rates (flagged estimated) or NULL —
74
+ // honest "cost unknown", never $0 (design D9).
75
+ if ((0, local_adapter_1.isLocalAdapter)(adapter)) {
76
+ if (nativeCostAbsent && hasBillableUsage) {
77
+ const computed = (0, pricing_1.estimateLocalCostUsd)(adapter.localConnection.rates, {
78
+ tokens_in: cloned.tokens_in,
79
+ tokens_out: cloned.tokens_out,
80
+ });
81
+ if (computed !== null) {
82
+ cloned.total_cost_usd = computed;
83
+ estimated = true;
84
+ }
85
+ }
86
+ return { result: cloned, estimated };
87
+ }
71
88
  if (nativeCostAbsent && hasBillableUsage) {
72
89
  const estimator = opts.estimator ?? pricing_1.estimateCostUsd;
73
90
  const computed = estimator(adapter.id, cloned.model, {
@@ -240,6 +240,7 @@ function composeReviewPacket({ db, row, repositoryId = row.repository_id ?? unde
240
240
  title: snap?.title ?? null,
241
241
  problem: narrative.problem,
242
242
  solution: narrative.solution,
243
+ solutionOverflow: narrative.overflow,
243
244
  labels: snap?.labels ?? [],
244
245
  implementationOutcome: unitRows[0]?.implementationOutcome ?? null,
245
246
  deliveryOutcome: unitRows[0]?.deliveryOutcome ?? null,
@@ -7,10 +7,17 @@
7
7
  "required": ["schemaVersion", "providers", "agents", "verification"],
8
8
  "properties": {
9
9
  "schemaVersion": { "const": 1 },
10
+ "efficiency": { "type": "object", "additionalProperties": false, "required": ["schemaVersion"], "properties": {
11
+ "schemaVersion": { "const": 1 }, "contextMode": { "enum": ["full", "incremental"] }, "reviewMode": { "enum": ["full", "incremental"] },
12
+ "planning": { "enum": ["full", "proportional"] }, "acceptDeveloperChecks": { "type": "boolean" },
13
+ "verification": { "type": "object", "additionalProperties": false, "properties": { "maxConcurrency": { "type": "integer", "minimum": 1, "maximum": 4 } } }
14
+ } },
15
+ "guardrails": { "type": "object", "additionalProperties": false, "properties": { "plan-validation": { "type": "boolean" }, "genuine-blocking-question": { "type": "boolean" }, "plan-language-hint": { "type": "boolean" }, "frozen-plan-writes": { "type": "boolean" }, "primary-language": { "type": "boolean" }, "duplicate-sibling": { "type": "boolean" }, "one-test-per-module": { "type": "boolean" }, "empty-write": { "type": "boolean" }, "evidence-gated-ticking": { "type": "boolean" }, "inventory-retry": { "type": "boolean" }, "synthetic-corrections": { "type": "boolean" }, "silent-group-closure": { "type": "boolean" }, "verify-per-group": { "type": "boolean" }, "test-reachability": { "type": "boolean" }, "exit-code-honesty": { "type": "boolean" }, "environment-repair": { "type": "boolean" }, "lockfile-repair": { "type": "boolean" }, "verify-idle-timeout": { "type": "boolean" } } },
10
16
  "rolePrompts": { "type": "object", "additionalProperties": false, "properties": {
11
17
  "architect": { "type": "string", "minLength": 1, "maxLength": 20000, "pattern": "\\S" },
12
18
  "developer": { "type": "string", "minLength": 1, "maxLength": 20000, "pattern": "\\S" },
13
- "reviewer": { "type": "string", "minLength": 1, "maxLength": 20000, "pattern": "\\S" }
19
+ "reviewer": { "type": "string", "minLength": 1, "maxLength": 20000, "pattern": "\\S" },
20
+ "fixer": { "type": "string", "minLength": 1, "maxLength": 20000, "pattern": "\\S" }
14
21
  } },
15
22
  "enabled": { "type": "boolean", "deprecated": true, "description": "Accepted for saved configuration compatibility; implementation always uses the agent runtime." },
16
23
  "providers": { "type": "array", "items": { "oneOf": [
@@ -20,9 +27,14 @@
20
27
  { "type": "object", "additionalProperties": false, "required": ["id", "kind", "baseUrl"], "properties": {
21
28
  "id": { "$ref": "#/definitions/id" }, "kind": { "const": "openai-compatible" },
22
29
  "baseUrl": { "type": "string", "pattern": "^https?://[^/?#@]+(?:/[^?#]*)?$" },
23
- "apiKeyEnv": { "type": "string", "pattern": "^[A-Za-z_][A-Za-z0-9_]*$" }
30
+ "apiKeyEnv": { "type": "string", "pattern": "^[A-Za-z_][A-Za-z0-9_]*$" },
31
+ "supportsReasoningEffort": { "type": "boolean", "description": "The endpoint honours the OpenAI reasoning_effort request field (low|medium|high)." },
32
+ "agentLoop": { "enum": ["compact", "free"], "description": "compact (default): host-driven pipelines of small structured calls for small local models; free: one agentic loop per role with guardrails." },
33
+ "contextWindowTokens": { "type": "integer", "minimum": 4096, "description": "Context window used for the compaction budget (chars/4 estimate). Default 32768." },
34
+ "maxOutputTokens": { "type": "integer", "minimum": 1024, "description": "Output budget (max_tokens) of one tool turn; retried once at twice this when cut off, always bounded by the remaining context window. Default 8192." }
24
35
  } }
25
36
  ] } },
37
+ "fixer": { "$ref": "#/definitions/agent" },
26
38
  "agents": { "type": "object", "additionalProperties": false, "required": ["architect", "developer", "reviewer"], "properties": {
27
39
  "architect": { "$ref": "#/definitions/agent" }, "developer": { "$ref": "#/definitions/agent" }, "reviewer": { "$ref": "#/definitions/agent" }
28
40
  } },
@@ -31,6 +43,7 @@
31
43
  "maxCostUsd": { "type": "number", "exclusiveMinimum": 0 }, "timeoutMs": { "$ref": "#/definitions/positiveInteger" }
32
44
  } },
33
45
  "verification": { "type": "array", "items": { "type": "object", "additionalProperties": false, "required": ["repositoryId", "command", "args"], "properties": {
46
+ "key": { "$ref": "#/definitions/id" }, "label": { "type": "string", "minLength": 1, "maxLength": 256, "pattern": "\\S" }, "policy": { "$ref": "#/definitions/checkPolicy" },
34
47
  "repositoryId": { "$ref": "#/definitions/id" }, "command": { "type": "string", "minLength": 1 },
35
48
  "args": { "type": "array", "items": { "type": "string" } }, "cwd": { "type": "string", "minLength": 1 },
36
49
  "env": { "type": "object", "propertyNames": { "pattern": "^[A-Za-z_][A-Za-z0-9_]*$" }, "additionalProperties": { "type": "string" } },
@@ -49,10 +62,22 @@
49
62
  } }
50
63
  },
51
64
  "definitions": {
65
+ "effort": { "type": "string", "pattern": "^[a-z][a-z0-9_-]{0,31}$" },
66
+ "inputList": { "type": "array", "maxItems": 256, "items": { "type": "string", "minLength": 1, "maxLength": 4096, "pattern": "\\S" } },
67
+ "checkPolicy": { "type": "object", "additionalProperties": false, "properties": {
68
+ "reuse": { "enum": ["never", "snapshot-local"] }, "inputs": { "$ref": "#/definitions/inputList" },
69
+ "toolchainInputs": { "$ref": "#/definitions/inputList" }, "resources": { "$ref": "#/definitions/inputList" },
70
+ "deterministic": { "type": "boolean" }, "readOnly": { "type": "boolean" }, "independentGroup": { "$ref": "#/definitions/id" }
71
+ } },
52
72
  "id": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$" },
53
73
  "positiveInteger": { "type": "integer", "minimum": 1 },
54
74
  "score": { "type": "number", "minimum": 0, "maximum": 100 },
55
- "agent": { "type": "object", "additionalProperties": false, "required": ["provider"], "properties": {
75
+ "agent": { "type": "object", "additionalProperties": false, "required": ["provider"], "dependencies": { "escalation": ["model"] }, "properties": {
76
+ "effort": { "$ref": "#/definitions/effort" },
77
+ "thinking": { "type": "string", "enum": ["on", "off"], "description": "OpenAI-compatible engines only: private thinking (chat_template_kwargs.enable_thinking). Default off." },
78
+ "escalation": { "type": "object", "additionalProperties": false, "required": ["model"], "properties": {
79
+ "model": { "type": "string", "minLength": 1, "maxLength": 256, "pattern": "^[^-\\r\\n\\u0000][^\\r\\n\\u0000]*$" }, "effort": { "$ref": "#/definitions/effort" }
80
+ } },
56
81
  "provider": { "$ref": "#/definitions/id" }, "model": { "type": "string", "minLength": 1, "maxLength": 256, "pattern": "^[^-\\r\\n\\u0000][^\\r\\n\\u0000]*$" },
57
82
  "maxTurns": { "$ref": "#/definitions/positiveInteger" }
58
83
  } }