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
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.writeRuntimeHistory = writeRuntimeHistory;
7
+ exports.readRuntimeHistory = readRuntimeHistory;
8
+ const node_fs_1 = __importDefault(require("node:fs"));
9
+ const node_path_1 = __importDefault(require("node:path"));
10
+ const node_crypto_1 = require("node:crypto");
11
+ const agent_runtime_metrics_1 = require("./agent-runtime-metrics");
12
+ const hash = (value) => (0, node_crypto_1.createHash)('sha256').update(value).digest('hex');
13
+ /** A replaceable projection, not an authority to resume, accept or deliver code. */
14
+ function writeRuntimeHistory(contextPath, input) {
15
+ const contextText = node_fs_1.default.readFileSync(contextPath, 'utf8'), context = JSON.parse(contextText);
16
+ let selection;
17
+ try {
18
+ selection = JSON.parse(node_fs_1.default.readFileSync(node_path_1.default.join(node_path_1.default.dirname(contextPath), 'desktop-runtime-selection.json'), 'utf8'));
19
+ }
20
+ catch { /* Old hosts have no explicit provenance. */ }
21
+ const result = { runId: context.runId, status: typeof input.status === 'string' ? input.status : 'failed', nextStep: typeof input.nextStep === 'string' ? input.nextStep : null,
22
+ updatedAt: new Date().toISOString(), ...(typeof input.error === 'string' ? { error: input.error.slice(0, 4000) } : {}),
23
+ metrics: (0, agent_runtime_metrics_1.readRuntimeEfficiency)(input.metrics), efficiencySummary: (0, agent_runtime_metrics_1.applyRuntimeSelectionOrigins)((0, agent_runtime_metrics_1.readRuntimeEfficiencySummary)(input.efficiencySummary), selection, context.runId), steps: {} };
24
+ const payload = JSON.stringify({ schemaVersion: 1, contextHash: hash(contextText), result });
25
+ const file = node_path_1.default.join(node_path_1.default.dirname(contextPath), 'desktop-runtime-history.json'), temporary = file + '.' + (0, node_crypto_1.randomUUID)() + '.tmp';
26
+ try {
27
+ node_fs_1.default.writeFileSync(temporary, JSON.stringify({ payload, integrity: hash(payload) }), { mode: 0o600, flag: 'wx' });
28
+ node_fs_1.default.renameSync(temporary, file);
29
+ }
30
+ finally {
31
+ node_fs_1.default.rmSync(temporary, { force: true });
32
+ }
33
+ }
34
+ function readRuntimeHistory(contextPath) {
35
+ const file = node_path_1.default.join(node_path_1.default.dirname(contextPath), 'desktop-runtime-history.json');
36
+ if (!node_fs_1.default.existsSync(file))
37
+ return null;
38
+ if (node_fs_1.default.lstatSync(file).isSymbolicLink() || node_fs_1.default.statSync(file).size > 128 * 1024)
39
+ throw new Error('Historical runtime projection is invalid');
40
+ const envelope = JSON.parse(node_fs_1.default.readFileSync(file, 'utf8'));
41
+ if (typeof envelope.payload !== 'string' || hash(envelope.payload) !== envelope.integrity)
42
+ throw new Error('Historical runtime projection integrity failed');
43
+ const value = JSON.parse(envelope.payload), contextText = node_fs_1.default.readFileSync(contextPath, 'utf8');
44
+ if (value.schemaVersion !== 1 || value.contextHash !== hash(contextText) || value.result?.runId !== JSON.parse(contextText).runId || !['running', 'succeeded', 'paused', 'failed', 'blocked', 'cancelled'].includes(value.result.status))
45
+ throw new Error('Historical runtime projection scope changed');
46
+ return { ...value.result, metrics: (0, agent_runtime_metrics_1.readRuntimeEfficiency)(value.result.metrics), efficiencySummary: (0, agent_runtime_metrics_1.readRuntimeEfficiencySummary)(value.result.efficiencySummary), steps: {} };
47
+ }
@@ -3,6 +3,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.findCoreAgentRuntimeEntry = findCoreAgentRuntimeEntry;
4
4
  exports.resetCoreAgentRuntimeApiCache = resetCoreAgentRuntimeApiCache;
5
5
  exports.loadCoreAgentRuntime = loadCoreAgentRuntime;
6
+ exports.requireRuntimeCapabilities = requireRuntimeCapabilities;
7
+ exports.validateRoleCapabilities = validateRoleCapabilities;
8
+ exports.validateRequestedRoleEfforts = validateRequestedRoleEfforts;
6
9
  exports.findCoreAgentRuntimeCli = findCoreAgentRuntimeCli;
7
10
  const node_fs_1 = require("node:fs");
8
11
  const node_child_process_1 = require("node:child_process");
@@ -11,6 +14,7 @@ const node_path_1 = require("node:path");
11
14
  const core_runtime_1 = require("./core-runtime");
12
15
  const core_node_runtime_1 = require("./core-node-runtime");
13
16
  const win_spawn_1 = require("./util/win-spawn");
17
+ const agent_runtime_package_1 = require("./agent-runtime-package");
14
18
  /** An explicit or bundled installation is authoritative: never silently replace
15
19
  * a broken production bundle with a developer's checkout. */
16
20
  function findCoreAgentRuntimeEntry() {
@@ -42,7 +46,7 @@ function findCoreAgentRuntimeEntry() {
42
46
  }
43
47
  return null;
44
48
  }
45
- /** The api probe is stable for a given CLI file; repeated settings saves need not respawn it.
49
+ /** The api probe is stable for executable package content; settings saves need not respawn it.
46
50
  * Validation stays uncached because its answer depends on the submitted configuration. */
47
51
  const apiProbeCache = new Map();
48
52
  function resetCoreAgentRuntimeApiCache() { apiProbeCache.clear(); }
@@ -57,17 +61,34 @@ async function loadCoreAgentRuntime() {
57
61
  cwd: (0, node_path_1.dirname)(cli), env: (0, win_spawn_1.windowsSpawnEnv)(process.env), input, encoding: 'utf8',
58
62
  timeout: 15_000, maxBuffer: 2 * 1024 * 1024, windowsHide: true, stdio: ['pipe', 'pipe', 'pipe'],
59
63
  });
60
- const stat = (0, node_fs_1.statSync)(cli);
64
+ const fingerprint = (0, agent_runtime_package_1.runtimeEntryFingerprint)(cli);
61
65
  const cached = apiProbeCache.get(cli);
62
- if (!cached || cached.mtimeMs !== stat.mtimeMs || cached.size !== stat.size) {
63
- const api = JSON.parse(invoke(['api']));
66
+ let api = cached?.fingerprint === fingerprint ? cached.api : undefined;
67
+ if (!api) {
68
+ api = JSON.parse(invoke(['api']));
64
69
  if (api.type !== 'runtime-api' || api.apiVersion !== 1) {
65
70
  throw new Error('Incompatible Core agent runtime API; expected version 1. Update the paired Core bundle.');
66
71
  }
67
- apiProbeCache.set(cli, { mtimeMs: stat.mtimeMs, size: stat.size });
72
+ if (api.capabilities !== undefined && (!api.capabilities || typeof api.capabilities !== 'object' || Array.isArray(api.capabilities) || Object.values(api.capabilities).some(value => !Number.isSafeInteger(value) || value < 1)))
73
+ throw new Error('Core returned malformed runtime capabilities');
74
+ if (api.workflowVersions !== undefined && (!Array.isArray(api.workflowVersions) || !api.workflowVersions.every(value => typeof value === 'string' && /^\d+$/.test(value))))
75
+ throw new Error('Core returned malformed workflow versions');
76
+ if (api.runtimeIdentity !== undefined) {
77
+ const identity = api.runtimeIdentity;
78
+ if (!identity || identity.apiVersion !== 1 || !['packageVersion', 'workflowVersion', 'instructionsVersion', 'packageIntegrity'].every(key => typeof identity[key] === 'string') || !/^sha256:[a-f0-9]{64}$/.test(identity.packageIntegrity))
79
+ throw new Error('Core returned malformed runtime identity');
80
+ }
81
+ apiProbeCache.set(cli, { fingerprint, api });
68
82
  }
69
83
  return {
70
84
  RUNTIME_API_VERSION: 1,
85
+ api,
86
+ capabilities(input) {
87
+ if (api.capabilities?.efficientRoleExecution !== 1)
88
+ throw new Error('Installed Core does not support role capability introspection. Update the paired Core package.');
89
+ const result = JSON.parse(invoke(['capabilities', '--stdin'], JSON.stringify(input)));
90
+ return { ...validateRoleCapabilities(result, input), runtimeIdentity: api.runtimeIdentity };
91
+ },
71
92
  rolePromptDefaults() {
72
93
  const result = JSON.parse(invoke(['prompts']));
73
94
  if (result.type !== 'runtime-role-prompts' || !['architect', 'developer', 'reviewer'].every(role => typeof result.defaults?.[role] === 'string' && result.defaults[role].trim()))
@@ -75,6 +96,7 @@ async function loadCoreAgentRuntime() {
75
96
  return result.defaults;
76
97
  },
77
98
  validateRuntimeConfig(input) {
99
+ requireRuntimeCapabilities(api, input);
78
100
  const result = JSON.parse(invoke(['validate', '--stdin'], JSON.stringify(input)));
79
101
  if (result.type !== 'runtime-config-valid')
80
102
  throw new Error('Core did not validate the runtime configuration');
@@ -82,6 +104,63 @@ async function loadCoreAgentRuntime() {
82
104
  },
83
105
  };
84
106
  }
107
+ function requireRuntimeCapabilities(api, input) {
108
+ if (!input || typeof input !== 'object' || Array.isArray(input))
109
+ return;
110
+ const config = input;
111
+ if ((config.efficiency !== undefined || Object.values(config.agents ?? {}).some(role => role?.effort !== undefined || role?.escalation !== undefined)) && api.capabilities?.efficientRoleExecution !== 1)
112
+ throw new Error('Requested role efficiency policy is unsupported by this Core runtime. Update the paired Core package.');
113
+ if (Array.isArray(config.verification) && config.verification.some(check => check?.policy !== undefined || check?.key !== undefined || check?.label !== undefined) && api.capabilities?.reproducibleVerification !== 1)
114
+ throw new Error('Requested verification policy is unsupported by this Core runtime. Update the paired Core package.');
115
+ }
116
+ /** Validate transport evidence against the exact submitted role selections. */
117
+ function validateRoleCapabilities(value, input) {
118
+ const invalid = () => { throw new Error('Core returned malformed role capabilities'); };
119
+ const object = (value) => value && typeof value === 'object' && !Array.isArray(value) ? value : invalid();
120
+ const result = object(value), config = object(input);
121
+ const choices = Object.entries(object(config.agents));
122
+ if (result.type !== 'runtime-capabilities' || result.schemaVersion !== 1 || !Array.isArray(result.roles) || result.roles.length > 6)
123
+ return invalid();
124
+ const expected = choices.flatMap(([role, value]) => {
125
+ const selected = object(value);
126
+ return [['base', selected], ...(selected.escalation ? [['escalation', object(selected.escalation)]] : [])].map(([tier, choice]) => {
127
+ const model = choice;
128
+ return { role, tier: String(tier), provider: selected.provider, model: model.model ?? null, requestedEffort: model.effort ?? null };
129
+ });
130
+ });
131
+ if (expected.length !== result.roles.length || expected.length < 3)
132
+ return invalid();
133
+ const seen = new Set();
134
+ const roles = result.roles.map(value => {
135
+ const row = object(value);
136
+ const selected = expected.find(item => Object.entries(item).every(([key, value]) => row[key] === value));
137
+ if (!selected)
138
+ return invalid();
139
+ const key = String(row.role) + ':' + String(row.tier);
140
+ if (seen.has(key))
141
+ return invalid();
142
+ seen.add(key);
143
+ if (typeof row.transport !== 'string' || !row.transport || row.transport.length > 128 || !['supported', 'unsupported', 'unknown'].includes(String(row.continuation)) || !['supported', 'unsupported', 'unknown'].includes(String(row.effortSupport)) || typeof row.observedModel !== 'boolean' || typeof row.observedEffort !== 'boolean')
144
+ return invalid();
145
+ if (row.supportedEfforts !== null && (!Array.isArray(row.supportedEfforts) || row.supportedEfforts.length > 32 || !row.supportedEfforts.every((level) => typeof level === 'string' && /^[a-z][a-z0-9_-]{0,31}$/.test(level))))
146
+ return invalid();
147
+ const levels = row.supportedEfforts;
148
+ if (row.effortSupport === 'supported' && !levels?.length || row.effortSupport === 'unknown' && levels !== null || row.effortSupport === 'unsupported' && levels?.length !== 0)
149
+ return invalid();
150
+ return { ...selected, transport: row.transport, continuation: row.continuation, effortSupport: row.effortSupport, supportedEfforts: levels, observedModel: row.observedModel, observedEffort: row.observedEffort };
151
+ });
152
+ return { type: 'runtime-capabilities', schemaVersion: 1, roles };
153
+ }
154
+ function validateRequestedRoleEfforts(runtime, config) {
155
+ if (!Object.values(config.agents).some(role => role.effort !== undefined || role.escalation?.effort !== undefined))
156
+ return;
157
+ if (!runtime.capabilities)
158
+ throw new Error('Installed Core cannot validate requested role effort');
159
+ const result = validateRoleCapabilities(runtime.capabilities(config), config);
160
+ for (const row of result.roles)
161
+ if (row.requestedEffort !== null && (row.effortSupport !== 'supported' || !row.supportedEfforts?.includes(String(row.requestedEffort))))
162
+ throw new Error(`${row.role} (${row.tier}): effort '${row.requestedEffort}' is not confirmed for ${row.transport}. Select provider default or a supported effort.`);
163
+ }
85
164
  function findCoreAgentRuntimeCli() {
86
165
  const entry = findCoreAgentRuntimeEntry();
87
166
  if (!entry)
@@ -1,9 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.readRuntimeEfficiency = readRuntimeEfficiency;
4
+ exports.readRuntimeEfficiencySummary = readRuntimeEfficiencySummary;
5
+ exports.applyRuntimeSelectionOrigins = applyRuntimeSelectionOrigins;
4
6
  const COUNTS = ['attempts', 'measuredAttempts', 'providerCalls', 'toolCalls', 'inputTokens', 'outputTokens', 'uncachedInputTokens', 'cacheReadInputTokens', 'cacheWriteInputTokens'];
5
7
  const KEYS = [...COUNTS, 'durationMs', 'agentDurationMs', 'costUsd'];
6
- const PHASES = ['architect', 'developer', 'verify', 'reviewer', 'archive'];
8
+ const PHASES = ['architect', 'developer', 'fixer', 'verify', 'reviewer', 'archive'];
7
9
  function object(value) { return value !== null && typeof value === 'object' && !Array.isArray(value) ? value : null; }
8
10
  function counters(value) {
9
11
  const input = object(value);
@@ -44,3 +46,77 @@ function readRuntimeEfficiency(value) {
44
46
  }
45
47
  return { schemaVersion: 1, total, phases };
46
48
  }
49
+ function readRuntimeEfficiencySummary(value) {
50
+ const input = object(value), invocations = object(input?.invocations), checks = object(input?.checks);
51
+ if (!input || input.schemaVersion !== 1 || typeof input.runId !== 'string' || input.runId.length > 128 || typeof input.workflowVersion !== 'string' || input.workflowVersion.length > 16 || !['pending', 'validated', 'with-exceptions', 'blocked'].includes(String(input.technicalAcceptance)) || !invocations || !checks || !Array.isArray(input.roles) || input.roles.length > 3)
52
+ return undefined;
53
+ if (typeof input.archive !== 'string' || input.archive.length > 32 || typeof input.delivery !== 'string' || input.delivery.length > 32 || typeof invocations.complete !== 'boolean' || typeof checks.available !== 'boolean')
54
+ return undefined;
55
+ const count = (value, nullable = true) => (nullable && value === null) || typeof value === 'number' && Number.isSafeInteger(value) && value >= 0;
56
+ for (const key of ['total', 'fullContexts', 'incrementalContexts'])
57
+ if (!count(invocations[key], false))
58
+ return undefined;
59
+ for (const key of ['promptBytes', 'contextBytes', 'handoffBytes'])
60
+ if (!count(invocations[key]))
61
+ return undefined;
62
+ if (checks.complete !== undefined && typeof checks.complete !== 'boolean' || checks.invalidated !== undefined && !count(checks.invalidated))
63
+ return undefined;
64
+ for (const key of ['executed', 'reused', 'notRun'])
65
+ if (!count(checks[key]))
66
+ return undefined;
67
+ if (checks.durationMs !== null && (typeof checks.durationMs !== 'number' || !Number.isFinite(checks.durationMs) || checks.durationMs < 0))
68
+ return undefined;
69
+ for (const key of ['planHash', 'candidateHash'])
70
+ if (input[key] !== null && (typeof input[key] !== 'string' || !/^[a-f0-9]{64}$/.test(input[key])))
71
+ return undefined;
72
+ if (!Array.isArray(input.currentEvidenceIds) || input.currentEvidenceIds.length > 100 || !input.currentEvidenceIds.every(id => typeof id === 'string' && /^[a-f0-9]{64}$/.test(id)))
73
+ return undefined;
74
+ const kinds = object(invocations.byKind);
75
+ if (!kinds || Object.keys(kinds).length !== 5 || !['initial', 'correction', 'repair', 'deepen', 'session-fallback'].every(key => count(kinds[key], false)))
76
+ return undefined;
77
+ const roles = [];
78
+ for (const raw of input.roles) {
79
+ const role = object(raw);
80
+ if (!role || !['architect', 'developer', 'reviewer'].includes(String(role.role)) || roles.some(item => item.role === role.role))
81
+ return undefined;
82
+ const row = { role: String(role.role) };
83
+ for (const key of ['provider', 'model', 'effort', 'observedModel', 'observedEffort', 'origin', 'tier']) {
84
+ if (role[key] !== null && (typeof role[key] !== 'string' || role[key].length > 256))
85
+ return undefined;
86
+ row[key] = role[key];
87
+ }
88
+ if (!['base', 'escalation'].includes(String(row.tier)) || typeof row.origin !== 'string')
89
+ return undefined;
90
+ roles.push(row);
91
+ }
92
+ let escalations;
93
+ if (input.escalations !== undefined) {
94
+ if (!Array.isArray(input.escalations) || input.escalations.length > 16 || typeof input.escalationsTruncated !== 'boolean')
95
+ return undefined;
96
+ escalations = [];
97
+ for (const item of input.escalations) {
98
+ const raw = object(item);
99
+ if (!raw || !['architect', 'developer', 'reviewer'].includes(String(raw.role)))
100
+ return undefined;
101
+ const row = { role: String(raw.role) };
102
+ for (const key of ['attemptId', 'provider', 'model', 'effort', 'reason']) {
103
+ if (raw[key] !== null && (typeof raw[key] !== 'string' || raw[key].length > 256) || ['attemptId', 'provider'].includes(key) && typeof raw[key] !== 'string')
104
+ return undefined;
105
+ row[key] = raw[key];
106
+ }
107
+ escalations.push(row);
108
+ }
109
+ }
110
+ return { ...(escalations ? { escalations, escalationsTruncated: input.escalationsTruncated } : {}), schemaVersion: 1, currentEvidenceIds: [...input.currentEvidenceIds], planHash: input.planHash, candidateHash: input.candidateHash, runId: input.runId, workflowVersion: input.workflowVersion, technicalAcceptance: input.technicalAcceptance, archive: input.archive, delivery: input.delivery, roles,
111
+ invocations: { total: invocations.total, complete: invocations.complete, byKind: { ...kinds }, promptBytes: invocations.promptBytes, contextBytes: invocations.contextBytes, handoffBytes: invocations.handoffBytes, fullContexts: invocations.fullContexts, incrementalContexts: invocations.incrementalContexts },
112
+ checks: { ...(checks.complete === undefined ? {} : { complete: checks.complete }), ...(checks.invalidated === undefined ? {} : { invalidated: checks.invalidated }), available: checks.available, executed: checks.executed, reused: checks.reused, notRun: checks.notRun, durationMs: checks.durationMs } };
113
+ }
114
+ /** Provenance is host-owned; it never changes Core's frozen provider selection. */
115
+ function applyRuntimeSelectionOrigins(summary, selection, runId) {
116
+ const record = object(selection), origins = object(record?.origins);
117
+ if (!summary || record?.schemaVersion !== 1 || record.runId !== runId || !origins)
118
+ return summary;
119
+ if (!['architect', 'developer', 'reviewer'].every(role => ['project-role', 'default', ...(role === 'developer' ? ['explicit-launch-override'] : [])].includes(String(origins[role]))))
120
+ return summary;
121
+ return { ...summary, roles: summary.roles.map(role => ({ ...role, origin: String(origins[role.role]) })) };
122
+ }
@@ -0,0 +1,205 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.runtimeEntryFingerprint = runtimeEntryFingerprint;
7
+ exports.retainAgentRuntime = retainAgentRuntime;
8
+ exports.resolveRetainedAgentRuntime = resolveRetainedAgentRuntime;
9
+ const node_crypto_1 = require("node:crypto");
10
+ const node_fs_1 = __importDefault(require("node:fs"));
11
+ const node_module_1 = require("node:module");
12
+ const node_path_1 = __importDefault(require("node:path"));
13
+ const SHIPPED = ['package.json', 'dist', 'bin', 'templates', 'schemas', 'commands', 'integration-contract.json', 'pinned-versions.json'];
14
+ const SAFE_PACKAGE = /^(?:@[a-z0-9._-]+\/)?[a-z0-9._-]+$/i;
15
+ function packageRoot(file, name) {
16
+ let directory = node_path_1.default.dirname(node_fs_1.default.realpathSync(file));
17
+ for (;;) {
18
+ const manifest = node_path_1.default.join(directory, 'package.json');
19
+ if (node_fs_1.default.existsSync(manifest)) {
20
+ const pkg = JSON.parse(node_fs_1.default.readFileSync(manifest, 'utf8'));
21
+ if (pkg.name === (name ?? 'specrails-core'))
22
+ return directory;
23
+ }
24
+ const parent = node_path_1.default.dirname(directory);
25
+ if (directory === parent)
26
+ throw new Error('Core runtime does not resolve to an identifiable package');
27
+ directory = parent;
28
+ }
29
+ }
30
+ /** Content identity also detects same-size edits with preserved timestamps. */
31
+ function runtimeEntryFingerprint(cli) {
32
+ let root;
33
+ try {
34
+ root = packageRoot(cli);
35
+ }
36
+ catch {
37
+ return (0, node_crypto_1.createHash)('sha256').update(node_fs_1.default.readFileSync(cli)).digest('hex');
38
+ }
39
+ return treeDigest(root, SHIPPED);
40
+ }
41
+ function treeDigest(root, entries = node_fs_1.default.readdirSync(root).sort()) {
42
+ const hash = (0, node_crypto_1.createHash)('sha256');
43
+ let files = 0, bytes = 0;
44
+ const visit = (relative) => {
45
+ const file = node_path_1.default.join(root, relative), stat = node_fs_1.default.lstatSync(file);
46
+ if (stat.isSymbolicLink())
47
+ throw new Error('Retained Core package contains a symbolic link');
48
+ if (stat.isDirectory())
49
+ for (const name of node_fs_1.default.readdirSync(file).sort())
50
+ visit(node_path_1.default.posix.join(relative, name));
51
+ else if (stat.isFile()) {
52
+ if (++files > 100_000 || (bytes += stat.size) > 1024 * 1024 * 1024)
53
+ throw new Error('Core runtime package exceeds retention limits');
54
+ hash.update(JSON.stringify([relative, stat.size])).update('\0').update(node_fs_1.default.readFileSync(file)).update('\0');
55
+ }
56
+ else
57
+ throw new Error('Core runtime package contains a nonregular file');
58
+ };
59
+ for (const entry of entries)
60
+ if (node_fs_1.default.existsSync(node_path_1.default.join(root, entry)))
61
+ visit(entry);
62
+ return hash.digest('hex');
63
+ }
64
+ /** Copy only the selected package and its installed production dependency
65
+ * closure. Resolution follows npm's actual nested versions, without downloads. */
66
+ function copyInstalledPackage(source, destination) {
67
+ const installed = new Map();
68
+ const copying = new Set();
69
+ let files = 0, bytes = 0, packages = 0;
70
+ const copy = (from, to, boundary) => {
71
+ const stat = node_fs_1.default.lstatSync(from);
72
+ if (stat.isSymbolicLink()) {
73
+ const resolved = node_fs_1.default.realpathSync(from), relative = node_path_1.default.relative(boundary, resolved);
74
+ if (relative.startsWith('..') || node_path_1.default.isAbsolute(relative))
75
+ throw new Error('Core package symlink escapes its package');
76
+ copy(resolved, to, boundary);
77
+ }
78
+ else if (stat.isDirectory()) {
79
+ const real = node_fs_1.default.realpathSync(from);
80
+ if (copying.has(real))
81
+ throw new Error('Core package contains a cyclic symbolic link');
82
+ copying.add(real);
83
+ node_fs_1.default.mkdirSync(to, { recursive: true });
84
+ try {
85
+ for (const name of node_fs_1.default.readdirSync(from).sort())
86
+ if (!['node_modules', '.git', '.bin'].includes(name))
87
+ copy(node_path_1.default.join(from, name), node_path_1.default.join(to, name), boundary);
88
+ }
89
+ finally {
90
+ copying.delete(real);
91
+ }
92
+ }
93
+ else if (stat.isFile()) {
94
+ if (++files > 100_000 || (bytes += stat.size) > 1024 * 1024 * 1024)
95
+ throw new Error('Core runtime package exceeds retention limits');
96
+ node_fs_1.default.mkdirSync(node_path_1.default.dirname(to), { recursive: true });
97
+ node_fs_1.default.copyFileSync(from, to);
98
+ node_fs_1.default.chmodSync(to, stat.mode & 0o777);
99
+ }
100
+ else
101
+ throw new Error('Core package contains a nonregular file');
102
+ };
103
+ const resolveDependency = (name, from) => {
104
+ const require = (0, node_module_1.createRequire)(node_path_1.default.join(from, 'package.json'));
105
+ try {
106
+ return packageRoot(require.resolve(name + '/package.json'), name);
107
+ }
108
+ catch {
109
+ return packageRoot(require.resolve(name), name);
110
+ }
111
+ };
112
+ const install = (from, to, root = false) => {
113
+ if (++packages > 2000)
114
+ throw new Error('Core dependency closure exceeds retention limits');
115
+ if (root)
116
+ for (const entry of SHIPPED) {
117
+ if (node_fs_1.default.existsSync(node_path_1.default.join(from, entry)))
118
+ copy(node_path_1.default.join(from, entry), node_path_1.default.join(to, entry), from);
119
+ }
120
+ else
121
+ copy(from, to, from);
122
+ installed.set(node_fs_1.default.realpathSync(to), from);
123
+ const pkg = JSON.parse(node_fs_1.default.readFileSync(node_path_1.default.join(from, 'package.json'), 'utf8'));
124
+ for (const name of Object.keys({ ...pkg.dependencies, ...pkg.optionalDependencies }).sort()) {
125
+ if (!SAFE_PACKAGE.test(name))
126
+ throw new Error('Invalid Core package dependency');
127
+ let dependency;
128
+ try {
129
+ dependency = resolveDependency(name, from);
130
+ }
131
+ catch (error) {
132
+ if (pkg.optionalDependencies?.[name])
133
+ continue;
134
+ throw error;
135
+ }
136
+ try {
137
+ const present = resolveDependency(name, to);
138
+ if (installed.get(present) === dependency)
139
+ continue;
140
+ }
141
+ catch { /* Not copied into this resolution scope yet. */ }
142
+ install(dependency, node_path_1.default.join(to, 'node_modules', name));
143
+ }
144
+ };
145
+ install(source, destination, true);
146
+ }
147
+ function retainAgentRuntime(cli, contextPath) {
148
+ const pinFile = node_path_1.default.join(node_path_1.default.dirname(contextPath), 'desktop-runtime-package.json');
149
+ if (node_fs_1.default.existsSync(pinFile))
150
+ return resolveRetainedAgentRuntime(contextPath);
151
+ const realCli = node_fs_1.default.realpathSync(cli);
152
+ const root = packageRoot(realCli);
153
+ const relativeCli = node_path_1.default.relative(root, realCli);
154
+ if (relativeCli.startsWith('..') || node_path_1.default.isAbsolute(relativeCli))
155
+ throw new Error('Core CLI escapes its package');
156
+ const cache = node_path_1.default.join(node_path_1.default.dirname(node_path_1.default.dirname(contextPath)), 'runtime-packages');
157
+ node_fs_1.default.mkdirSync(cache, { recursive: true, mode: 0o700 });
158
+ const staged = node_path_1.default.join(cache, '.staged-' + (0, node_crypto_1.randomUUID)());
159
+ try {
160
+ copyInstalledPackage(root, staged);
161
+ const integrity = treeDigest(staged);
162
+ const retained = node_path_1.default.join(cache, integrity);
163
+ if (node_fs_1.default.existsSync(retained)) {
164
+ if (treeDigest(retained) !== integrity)
165
+ throw new Error('Retained Core runtime integrity changed');
166
+ }
167
+ else {
168
+ try {
169
+ node_fs_1.default.renameSync(staged, retained);
170
+ }
171
+ catch (error) {
172
+ if (!node_fs_1.default.existsSync(retained) || treeDigest(retained) !== integrity)
173
+ throw error;
174
+ }
175
+ }
176
+ const pin = { schemaVersion: 1, packageVersion: JSON.parse(node_fs_1.default.readFileSync(node_path_1.default.join(root, 'package.json'), 'utf8')).version, integrity, root: retained, cli: relativeCli };
177
+ try {
178
+ node_fs_1.default.writeFileSync(pinFile, JSON.stringify(pin) + '\n', { flag: 'wx', mode: 0o600 });
179
+ }
180
+ catch (error) {
181
+ if (error.code !== 'EEXIST' || resolveRetainedAgentRuntime(contextPath) !== node_path_1.default.join(retained, relativeCli))
182
+ throw error;
183
+ }
184
+ return node_path_1.default.join(retained, relativeCli);
185
+ }
186
+ finally {
187
+ node_fs_1.default.rmSync(staged, { recursive: true, force: true });
188
+ }
189
+ }
190
+ function resolveRetainedAgentRuntime(contextPath) {
191
+ const pinFile = node_path_1.default.join(node_path_1.default.dirname(contextPath), 'desktop-runtime-package.json');
192
+ if (!node_fs_1.default.existsSync(pinFile))
193
+ throw new Error('Original runtime identity is unrecorded. Restore a proven original Core package before continuing this saved execution.');
194
+ const pin = JSON.parse(node_fs_1.default.readFileSync(pinFile, 'utf8'));
195
+ const expected = node_path_1.default.join(node_path_1.default.dirname(node_path_1.default.dirname(contextPath)), 'runtime-packages', pin.integrity);
196
+ if (pin.schemaVersion !== 1 || !/^[a-f0-9]{64}$/.test(pin.integrity) || pin.root !== expected || !node_fs_1.default.existsSync(expected) || node_fs_1.default.realpathSync(expected) !== node_path_1.default.resolve(expected)
197
+ || typeof pin.cli !== 'string' || node_path_1.default.isAbsolute(pin.cli) || pin.cli.split(/[\\/]/).includes('..'))
198
+ throw new Error('The original retained Core package is unavailable or invalid. Restore it before continuing.');
199
+ if (treeDigest(expected) !== pin.integrity)
200
+ throw new Error('The original retained Core package failed its integrity check. Restore it before continuing.');
201
+ const cli = node_path_1.default.join(expected, pin.cli);
202
+ if (!node_fs_1.default.statSync(cli).isFile())
203
+ throw new Error('The original Core executable is unavailable');
204
+ return cli;
205
+ }
@@ -4,15 +4,70 @@ exports.registerAgentRuntimeSettingsRoutes = registerAgentRuntimeSettingsRoutes;
4
4
  const node_fs_1 = require("node:fs");
5
5
  const agent_runtime_loader_1 = require("./agent-runtime-loader");
6
6
  const agent_runtime_settings_1 = require("./agent-runtime-settings");
7
+ const agent_runtime_settings_2 = require("./agent-runtime-settings");
7
8
  const agent_runtime_verification_suggestions_1 = require("./agent-runtime-verification-suggestions");
8
9
  const project_repositories_1 = require("./project-repositories");
10
+ const agent_runtime_effective_config_1 = require("./agent-runtime-effective-config");
11
+ /** Raw request body → same body with every local role's missing model filled (validation then passes). */
12
+ function withDefaultRoleModels(body) {
13
+ const agents = body.agents;
14
+ const providers = body.providers;
15
+ if (!agents || typeof agents !== 'object' || !Array.isArray(providers))
16
+ return body;
17
+ // Only LOCAL roles: a CLI role saved without a model keeps meaning "provider default" (never frozen into the file).
18
+ const fixer = body.fixer && typeof body.fixer === 'object' ? structuredClone(body.fixer) : undefined;
19
+ const filled = (0, agent_runtime_effective_config_1.fillDefaultRoleModels)({ agents: structuredClone(agents), providers: providers, ...(fixer ? { fixer: fixer } : {}) }, { localOnly: true });
20
+ return { ...body, agents: filled.agents, ...(filled.fixer ? { fixer: filled.fixer } : {}) };
21
+ }
22
+ const loop_role_engines_1 = require("./loop-role-engines");
9
23
  function registerAgentRuntimeSettingsRoutes({ router, ctx }) {
10
- router.get('/:projectId/agent-runtime/config', (_req, res) => {
24
+ /** Guardrail catalog of the loaded Core (configurable-guardrails): ids + phase; the UI owns labels. `supported:false` on older cores. */
25
+ router.get('/:projectId/agent-runtime/guardrails', async (_req, res) => {
26
+ try {
27
+ const runtime = await (0, agent_runtime_loader_1.loadCoreAgentRuntime)();
28
+ const supported = runtime.api?.capabilities?.configurableGuardrails === 1;
29
+ const catalog = supported && Array.isArray(runtime.api?.guardrails) ? runtime.api.guardrails.filter((item) => item && typeof item.id === 'string' && typeof item.phase === 'string') : [];
30
+ res.json({ supported, catalog });
31
+ }
32
+ catch {
33
+ res.json({ supported: false, catalog: [] });
34
+ }
35
+ });
36
+ /** Loop-side role engines (verifier / decider) for `roles` rail launches — hybrid-role-engines. */
37
+ router.get('/:projectId/agent-runtime/loop-roles', (req, res) => {
38
+ try {
39
+ res.json({ roles: (0, loop_role_engines_1.loadLoopRoleEngines)(ctx(req).project) });
40
+ }
41
+ catch {
42
+ res.status(500).json({ error: 'loop_roles_read_failed' });
43
+ }
44
+ });
45
+ router.put('/:projectId/agent-runtime/loop-roles', (req, res) => {
46
+ try {
47
+ const project = ctx(req).project;
48
+ const roles = (0, loop_role_engines_1.validateLoopRoleEngines)(req.body?.roles ?? req.body ?? {}, project);
49
+ (0, loop_role_engines_1.saveLoopRoleEngines)(project, roles);
50
+ res.json({ roles });
51
+ }
52
+ catch (error) {
53
+ if (error instanceof loop_role_engines_1.LoopRoleEnginesError) {
54
+ res.status(400).json({ error: 'invalid_loop_roles', message: error.message });
55
+ return;
56
+ }
57
+ res.status(500).json({ error: 'loop_roles_write_failed' });
58
+ }
59
+ });
60
+ router.get('/:projectId/agent-runtime/config', async (_req, res) => {
11
61
  try {
12
62
  const project = ctx(_req).project;
13
63
  const configured = (0, node_fs_1.existsSync)((0, agent_runtime_settings_1.agentRuntimeConfigPath)(project));
14
64
  const config = (0, agent_runtime_settings_1.loadAgentRuntimeConfig)(project);
15
- res.json({ configured, config, runtimeAvailable: (0, agent_runtime_loader_1.findCoreAgentRuntimeEntry)() !== null });
65
+ let efficiencyAvailable = false;
66
+ try {
67
+ efficiencyAvailable = (await (0, agent_runtime_loader_1.loadCoreAgentRuntime)()).api?.capabilities?.efficientRoleExecution === 1;
68
+ }
69
+ catch { /* Ordinary settings remain readable without capability support. */ }
70
+ res.json({ configured, config, runtimeAvailable: (0, agent_runtime_loader_1.findCoreAgentRuntimeEntry)() !== null, efficiencyAvailable });
16
71
  }
17
72
  catch (err) {
18
73
  const validation = err instanceof agent_runtime_settings_1.AgentRuntimeConfigError;
@@ -32,18 +87,54 @@ function registerAgentRuntimeSettingsRoutes({ router, ctx }) {
32
87
  res.status(500).json({ error: 'verification_suggestions_failed', message: 'Could not inspect the project repositories' });
33
88
  }
34
89
  });
90
+ router.post('/:projectId/agent-runtime/capabilities', async (req, res) => {
91
+ try {
92
+ const config = (0, agent_runtime_settings_1.validateAgentRuntimeConfig)(withDefaultRoleModels({ ...req.body, providers: (0, agent_runtime_settings_1.loadRuntimeProviders)(), enabled: true }));
93
+ const runtime = await (0, agent_runtime_loader_1.loadCoreAgentRuntime)();
94
+ if (!runtime.capabilities)
95
+ throw new Error('Installed Core does not support capability introspection');
96
+ // Core's schema is additionalProperties:false — strip the desktop-only
97
+ // connection fields (label/defaultModel/rates/supportsReasoningEffort) first.
98
+ res.json(await runtime.capabilities({ ...(0, agent_runtime_settings_2.forCoreRuntime)(config, runtime.api?.capabilities), providers: (0, agent_runtime_settings_2.stripDesktopConnectionFields)(config.providers, (0, agent_runtime_settings_1.coreConnectionFieldGates)(runtime.api?.capabilities)) }));
99
+ }
100
+ catch (error) {
101
+ // Core prints its verdict (`{"type":"runtime-result","status":"failed","error":…}`) before exiting non-zero: show that, not the generic "update Core" line.
102
+ const coreMessage = (() => { const out = error.stdout; if (typeof out !== 'string')
103
+ return undefined; try {
104
+ const parsed = JSON.parse(out.trim().split('\n').pop() ?? '');
105
+ return typeof parsed.error === 'string' ? parsed.error : undefined;
106
+ }
107
+ catch {
108
+ return undefined;
109
+ } })();
110
+ res.status(error instanceof agent_runtime_settings_1.AgentRuntimeConfigError || coreMessage ? 400 : 503).json({ error: 'runtime_capabilities_unavailable', message: error instanceof agent_runtime_settings_1.AgentRuntimeConfigError ? error.message : coreMessage ?? 'Update the paired Core runtime to inspect model capabilities' });
111
+ }
112
+ });
35
113
  router.put('/:projectId/agent-runtime/config', async (req, res) => {
36
114
  try {
37
- (0, agent_runtime_settings_1.validateAgentRuntimeConfig)(req.body);
115
+ // A local role saved as "provider default" carries no model; fill the
116
+ // connection default (the launch rule) before the model-required check.
117
+ // The raw body is validated first (its own providers included — a
118
+ // credential in a submitted URL must be refused), then the stored
119
+ // connections replace them and the missing local-role models are filled
120
+ // from each connection's default before the model-required check.
121
+ // Saved WITHOUT filling local-role models: "provider default" stays a
122
+ // live reference to the connection's default (resolved at launch).
123
+ (0, agent_runtime_settings_1.validateAgentRuntimeConfig)({ ...req.body, providers: Array.isArray(req.body?.providers) ? req.body.providers : (0, agent_runtime_settings_1.loadRuntimeProviders)() });
38
124
  const config = (0, agent_runtime_settings_1.validateAgentRuntimeConfig)({ ...req.body, enabled: true, providers: (0, agent_runtime_settings_1.loadRuntimeProviders)() });
39
125
  const runtimeAvailable = (0, agent_runtime_loader_1.findCoreAgentRuntimeEntry)() !== null;
40
126
  if (config.enabled && !runtimeAvailable) {
41
127
  res.status(503).json({ error: 'runtime_unavailable', message: 'Update Core to an installation with the agent runtime before enabling it' });
42
128
  return;
43
129
  }
130
+ let efficiencyAvailable = false;
44
131
  if (config.enabled && runtimeAvailable) {
45
132
  try {
46
- (await (0, agent_runtime_loader_1.loadCoreAgentRuntime)()).validateRuntimeConfig(config);
133
+ const runtime = await (0, agent_runtime_loader_1.loadCoreAgentRuntime)();
134
+ const coreConfig = { ...(0, agent_runtime_settings_2.forCoreRuntime)((0, agent_runtime_effective_config_1.fillDefaultRoleModels)(structuredClone(config), { localOnly: true }), runtime.api?.capabilities), providers: (0, agent_runtime_settings_2.stripDesktopConnectionFields)(config.providers, (0, agent_runtime_settings_1.coreConnectionFieldGates)(runtime.api?.capabilities)) };
135
+ runtime.validateRuntimeConfig(coreConfig);
136
+ (0, agent_runtime_loader_1.validateRequestedRoleEfforts)(runtime, coreConfig);
137
+ efficiencyAvailable = runtime.api?.capabilities?.efficientRoleExecution === 1;
47
138
  }
48
139
  catch {
49
140
  res.status(503).json({ error: 'runtime_incompatible', message: 'The installed Core runtime could not validate this configuration; update Core and retry' });
@@ -51,7 +142,7 @@ function registerAgentRuntimeSettingsRoutes({ router, ctx }) {
51
142
  }
52
143
  }
53
144
  const saved = (0, agent_runtime_settings_1.saveAgentRuntimeConfig)(ctx(req).project, config);
54
- res.json({ configured: true, config: saved, runtimeAvailable });
145
+ res.json({ configured: true, config: saved, runtimeAvailable, efficiencyAvailable });
55
146
  }
56
147
  catch (err) {
57
148
  const validation = err instanceof agent_runtime_settings_1.AgentRuntimeConfigError;