specrails-desktop 2.36.0 → 2.42.2

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 (510) hide show
  1. package/README.md +94 -247
  2. package/client/dist/assets/ActivityFeedPage-DoEMumQS.js +1 -0
  3. package/client/dist/assets/AgentBrowserCapture-DIh4K0gh.js +1 -0
  4. package/client/dist/assets/AgentModeAnalyticsPane-mbq4WEoO.js +2 -0
  5. package/client/dist/assets/AgentModeCodePane-CKh7zuA4.js +2 -0
  6. package/client/dist/assets/AgentModeJobsPane-BDokPiLJ.js +1 -0
  7. package/client/dist/assets/AgentsPage-BFVAZbBj.js +87 -0
  8. package/client/dist/assets/AnalyticsPage-3Y9LuWwz.js +1 -0
  9. package/client/dist/assets/CodePage-C9KyuV_C.js +3 -0
  10. package/client/dist/assets/DesktopAnalyticsPage-Bhi54bXQ.js +1 -0
  11. package/client/dist/assets/{DocsDialog-C76VdBJC.js → DocsDialog-Dj6mt4ga.js} +2 -2
  12. package/client/dist/assets/{DocsPage-ziMlj6B6.js → DocsPage-24yWglNI.js} +1 -1
  13. package/client/dist/assets/ExportDropdown-ophapHrT.js +1 -0
  14. package/client/dist/assets/InteractiveJobComposer-C1ucmxQh.js +19 -0
  15. package/client/dist/assets/JobDetailModal-DNWmgVHD.js +1 -0
  16. package/client/dist/assets/JobDetailPage-BcajZyuu.js +1 -0
  17. package/client/dist/assets/JobsPage-B9nTZw6e.js +1 -0
  18. package/client/dist/assets/LoopBuilderPage-Oc48iOFW.js +7 -0
  19. package/client/dist/assets/LoopPreviewModal-ae0Fr6SU.js +1 -0
  20. package/client/dist/assets/LoopsPage-CUEqzUBI.js +1 -0
  21. package/client/dist/assets/MinimizedChatsContext-Deg39peL.js +1 -0
  22. package/client/dist/assets/PluginsPage-C1xtARIx.js +2 -0
  23. package/client/dist/assets/ProjectSettingsDialog-JZzrXFBN.js +1 -0
  24. package/client/dist/assets/RepositoryDeliveries-CSDsiVUz.js +5 -0
  25. package/client/dist/assets/RepositoryScopeSelector-D-RCaknl.js +1 -0
  26. package/client/dist/assets/ReviewPacketPage-x5EfGTiL.js +1 -0
  27. package/client/dist/assets/TemplatePreviewModal-BSyuZxGq.js +1 -0
  28. package/client/dist/assets/TicketDetailModalContext-DqyF0so-.js +14 -0
  29. package/client/dist/assets/Trans-SnMX4MhW.js +1 -0
  30. package/client/dist/assets/agent-BdT0SB1D.js +1 -0
  31. package/client/dist/assets/agent-Bh9K7iEt.js +1 -0
  32. package/client/dist/assets/agent-C4ibdFk9.js +1 -0
  33. package/client/dist/assets/agent-CSFY22Zo.js +1 -0
  34. package/client/dist/assets/agent-DAnqxUn8.js +1 -0
  35. package/client/dist/assets/agent-DTQiu0Mi.js +1 -0
  36. package/client/dist/assets/agent-T54WuR_Y.js +1 -0
  37. package/client/dist/assets/agent-i75WDZOA.js +1 -0
  38. package/client/dist/assets/browser-05vRDg_y.js +1 -0
  39. package/client/dist/assets/browser-Bulb-cB1.js +1 -0
  40. package/client/dist/assets/browser-C7f-Jsg1.js +1 -0
  41. package/client/dist/assets/browser-CG0PE1j_.js +1 -0
  42. package/client/dist/assets/browser-CMzaqAjo.js +1 -0
  43. package/client/dist/assets/browser-DMajYdNX.js +1 -0
  44. package/client/dist/assets/browser-DxcOHrdP.js +1 -0
  45. package/client/dist/assets/browser-ObE7xVo3.js +1 -0
  46. package/client/dist/assets/builder-765NeYTp.js +1 -0
  47. package/client/dist/assets/builder-8MG3V6ea.js +1 -0
  48. package/client/dist/assets/builder-B-1UBs5K.js +1 -0
  49. package/client/dist/assets/builder-BTiGnPT0.js +1 -0
  50. package/client/dist/assets/builder-Cx9Rz-7u.js +1 -0
  51. package/client/dist/assets/builder-DKFMELBl.js +1 -0
  52. package/client/dist/assets/builder-OcmKrC0h.js +1 -0
  53. package/client/dist/assets/builder-P9qVBXFZ.js +1 -0
  54. package/client/dist/assets/{button-CJqFnUOL.js → button-DCBqEaZl.js} +1 -1
  55. package/client/dist/assets/{code-CkUuW4m9.js → code-B37GRg9C.js} +1 -1
  56. package/client/dist/assets/{code-B0O8-p8k.js → code-BBOEZbf9.js} +1 -1
  57. package/client/dist/assets/{code-B1VLZDhS.js → code-BRm-jcyx.js} +1 -1
  58. package/client/dist/assets/{code-Bpj4Ddml.js → code-CF7w62CR.js} +1 -1
  59. package/client/dist/assets/{code-BykBZ5-e.js → code-CLuvJt2r.js} +1 -1
  60. package/client/dist/assets/{code-B86IMAfz.js → code-D0RCm59f.js} +1 -1
  61. package/client/dist/assets/code-DaZ-ZwCT.js +1 -0
  62. package/client/dist/assets/{code-cAqaqMTx.js → code-iIHkGaCL.js} +1 -1
  63. package/client/dist/assets/commands-0XjAftla.js +1 -0
  64. package/client/dist/assets/commands-BAWSYHjQ.js +1 -0
  65. package/client/dist/assets/commands-BBwowgiE.js +1 -0
  66. package/client/dist/assets/commands-BacjIZLN.js +1 -0
  67. package/client/dist/assets/commands-Bs62IAZ3.js +1 -0
  68. package/client/dist/assets/commands-CLPdh-Hh.js +1 -0
  69. package/client/dist/assets/commands-DlbFzgrp.js +1 -0
  70. package/client/dist/assets/commands-LjPSsi5F.js +1 -0
  71. package/client/dist/assets/common-B5AZNYMH.js +1 -0
  72. package/client/dist/assets/common-BFZ0c_vN.js +1 -0
  73. package/client/dist/assets/common-BrlwEnL7.js +1 -0
  74. package/client/dist/assets/common-BttQx3z4.js +1 -0
  75. package/client/dist/assets/common-CM65k_-e.js +1 -0
  76. package/client/dist/assets/common-D4mvFFlu.js +1 -0
  77. package/client/dist/assets/common-TZ9Ny96S.js +1 -0
  78. package/client/dist/assets/common-nFpSF4GC.js +1 -0
  79. package/client/dist/assets/dashboard-B14uYXkG.js +1 -0
  80. package/client/dist/assets/dashboard-BK_mez-z.js +1 -0
  81. package/client/dist/assets/dashboard-BfBir_6o.js +1 -0
  82. package/client/dist/assets/dashboard-Bt0cCzeG.js +1 -0
  83. package/client/dist/assets/dashboard-BwkjxI6H.js +1 -0
  84. package/client/dist/assets/{dashboard-C8WOEiPu.js → dashboard-C3bDKf7s.js} +1 -1
  85. package/client/dist/assets/dashboard-CdvjGfog.js +1 -0
  86. package/client/dist/assets/dashboard-fRrP0Kv0.js +1 -0
  87. package/client/dist/assets/dialog-SRw01_QU.js +45 -0
  88. package/client/dist/assets/dist-js-CJ_2XX1Y.js +1 -0
  89. package/client/dist/assets/formatDistanceToNow-DSj5DprF.js +1 -0
  90. package/client/dist/assets/getTimezoneOffsetInMilliseconds-YhD8rIEs.js +1 -0
  91. package/client/dist/assets/index-DwAmL7m5.css +2 -0
  92. package/client/dist/assets/index-EzrkUb8B.js +76 -0
  93. package/client/dist/assets/{jira-api-B1-MUehg.js → jira-api-CSCqGr-Q.js} +1 -1
  94. package/client/dist/assets/{jobs-CN_zv4Gr.js → jobs-B8rrzfyZ.js} +1 -1
  95. package/client/dist/assets/{jobs-DUlWSYq2.js → jobs-BS6RTurf.js} +1 -1
  96. package/client/dist/assets/{jobs-Bpks6doU.js → jobs-BTT0Bsks.js} +1 -1
  97. package/client/dist/assets/{jobs-Bgq-PZkC.js → jobs-CezU69AE.js} +1 -1
  98. package/client/dist/assets/{jobs-SwYS2M-B.js → jobs-ClFJae0s.js} +1 -1
  99. package/client/dist/assets/{jobs-BAls3bWq.js → jobs-DJSFIFVy.js} +1 -1
  100. package/client/dist/assets/{jobs-DhxKgWFq.js → jobs-DhgGr8vm.js} +1 -1
  101. package/client/dist/assets/{jobs-B_TeSQxb.js → jobs-DzMjUcaN.js} +1 -1
  102. package/client/dist/assets/mcp-6rMoZfEO.js +1 -0
  103. package/client/dist/assets/mcp-BD8sL5mP.js +1 -0
  104. package/client/dist/assets/mcp-BkcA3I5s.js +1 -0
  105. package/client/dist/assets/mcp-CCpSelOh.js +1 -0
  106. package/client/dist/assets/mcp-CUAN6ORK.js +1 -0
  107. package/client/dist/assets/mcp-CqbRxRTE.js +1 -0
  108. package/client/dist/assets/mcp-DP2YfVhO.js +1 -0
  109. package/client/dist/assets/mcp-DUVQamDz.js +1 -0
  110. package/client/dist/assets/narration-B-ynWTpn.js +1 -0
  111. package/client/dist/assets/narration-Bj_laZwg.js +1 -0
  112. package/client/dist/assets/narration-BxHpmMv_.js +1 -0
  113. package/client/dist/assets/narration-C1Px0-0j.js +1 -0
  114. package/client/dist/assets/narration-CAXhGbzM.js +1 -0
  115. package/client/dist/assets/narration-DQ1l_Kq6.js +1 -0
  116. package/client/dist/assets/narration-XISXwqZd.js +1 -0
  117. package/client/dist/assets/narration-kwzfglQo.js +1 -0
  118. package/client/dist/assets/nav-1-Hh2kOz.js +1 -0
  119. package/client/dist/assets/nav-B34QkESm.js +1 -0
  120. package/client/dist/assets/nav-Bap_UXAq.js +1 -0
  121. package/client/dist/assets/{nav-pYuXq7sf.js → nav-BwxXIB24.js} +1 -1
  122. package/client/dist/assets/nav-Ch-zHBG5.js +1 -0
  123. package/client/dist/assets/nav-CygC_WN-.js +1 -0
  124. package/client/dist/assets/{nav-iwjCg5FR.js → nav-KnnLQtOQ.js} +1 -1
  125. package/client/dist/assets/nav-m00uxYUG.js +1 -0
  126. package/client/dist/assets/packet-BQ2tXfJI.js +1 -0
  127. package/client/dist/assets/packet-BdpOJ1Cy.js +1 -0
  128. package/client/dist/assets/packet-C3Ievb9R.js +1 -0
  129. package/client/dist/assets/packet-C4zJIG0Y.js +1 -0
  130. package/client/dist/assets/packet-Cx-zRuWn.js +1 -0
  131. package/client/dist/assets/packet-DUTGX52M.js +1 -0
  132. package/client/dist/assets/packet-DhsKtUPB.js +1 -0
  133. package/client/dist/assets/packet-z3LJ8Zyy.js +1 -0
  134. package/client/dist/assets/project-repositories-DYhTvwQ1.js +1 -0
  135. package/client/dist/assets/provider-capabilities-B-wOHfLP.js +1 -0
  136. package/client/dist/assets/settings-BBt2xtDQ.js +1 -0
  137. package/client/dist/assets/settings-BJB9RKJj.js +1 -0
  138. package/client/dist/assets/settings-CHwQR2rB.js +1 -0
  139. package/client/dist/assets/settings-CrMi3kSi.js +1 -0
  140. package/client/dist/assets/settings-DKyhDGD1.js +1 -0
  141. package/client/dist/assets/settings-Deupji9c.js +1 -0
  142. package/client/dist/assets/settings-DqRMmwAM.js +1 -0
  143. package/client/dist/assets/settings-J4rj6vo-.js +1 -0
  144. package/client/dist/assets/setup-2xGgu4Sk.js +1 -0
  145. package/client/dist/assets/setup-Ba11l2JA.js +1 -0
  146. package/client/dist/assets/setup-BzW4kMok.js +1 -0
  147. package/client/dist/assets/setup-COzwYhkJ.js +1 -0
  148. package/client/dist/assets/setup-CRVULhtF.js +1 -0
  149. package/client/dist/assets/setup-DgvlxwpE.js +1 -0
  150. package/client/dist/assets/setup-Dw_wYeEY.js +1 -0
  151. package/client/dist/assets/setup-ZPtEeJPW.js +1 -0
  152. package/client/dist/assets/spending-BqbdPm_4.js +1 -0
  153. package/client/dist/assets/square-Ci-PFcFe.js +1 -0
  154. package/client/dist/assets/{terminal-BFRZnMPP.js → terminal-BAzpHtpY.js} +1 -1
  155. package/client/dist/assets/{terminal-IahE4LW9.js → terminal-C7dHeR3x.js} +1 -1
  156. package/client/dist/assets/{terminal-tITa81iz.js → terminal-CMla5Qsz.js} +1 -1
  157. package/client/dist/assets/{terminal-BZiIu3rf.js → terminal-CdNXH-n9.js} +1 -1
  158. package/client/dist/assets/{terminal-ScjyXNxQ.js → terminal-Cw5bZsmV.js} +1 -1
  159. package/client/dist/assets/{terminal-C2NAd7BW.js → terminal-DFUP4eNv.js} +1 -1
  160. package/client/dist/assets/{terminal-d7GadXbN.js → terminal-DGqljeK_.js} +1 -1
  161. package/client/dist/assets/{terminal-QpXZU2Bn.js → terminal-cU70NodO.js} +1 -1
  162. package/client/dist/assets/useDesktop-RSMTyy7V.js +1 -0
  163. package/client/dist/assets/useSharedWebSocket-BOdS5_Iz.js +2 -0
  164. package/client/dist/index.html +34 -35
  165. package/docs/README.md +3 -0
  166. package/docs/agent-live-steering.md +57 -0
  167. package/docs/ci-cd.md +57 -0
  168. package/docs/code-explorer.md +48 -0
  169. package/docs/codex.md +12 -1
  170. package/docs/features/detachable-mission-windows.md +59 -0
  171. package/docs/guide/de/integrations/5-mcp-server.md +13 -9
  172. package/docs/guide/de/integrations/6-agent-chat.md +1 -0
  173. package/docs/guide/en/integrations/5-mcp-server.md +13 -9
  174. package/docs/guide/en/integrations/6-agent-chat.md +1 -0
  175. package/docs/guide/es/integrations/5-mcp-server.md +13 -9
  176. package/docs/guide/es/integrations/6-agent-chat.md +1 -0
  177. package/docs/guide/fr/integrations/5-mcp-server.md +13 -9
  178. package/docs/guide/fr/integrations/6-agent-chat.md +1 -0
  179. package/docs/guide/it/integrations/5-mcp-server.md +13 -9
  180. package/docs/guide/it/integrations/6-agent-chat.md +1 -0
  181. package/docs/guide/ja/integrations/5-mcp-server.md +13 -9
  182. package/docs/guide/ja/integrations/6-agent-chat.md +1 -0
  183. package/docs/guide/pt/integrations/5-mcp-server.md +13 -9
  184. package/docs/guide/pt/integrations/6-agent-chat.md +1 -0
  185. package/docs/guide/zh/integrations/5-mcp-server.md +13 -9
  186. package/docs/guide/zh/integrations/6-agent-chat.md +1 -0
  187. package/docs/internals/README.md +1 -0
  188. package/docs/internals/browser-capture-performance.md +55 -3
  189. package/docs/internals/browser-login-popups.md +45 -0
  190. package/docs/internals/browser-native-retina-audit.md +39 -0
  191. package/docs/internals/ci-cd-audit.md +39 -0
  192. package/docs/internals/core-runtime-updates.md +68 -0
  193. package/docs/internals/embedded-browser-native-webview-evaluation.md +2 -0
  194. package/docs/internals/implementation-reliability-audit.md +93 -0
  195. package/docs/internals/interactive-jobs.md +110 -6
  196. package/docs/internals/mcp-mission-audit.md +76 -0
  197. package/docs/internals/project-builder.md +404 -42
  198. package/docs/internals/safe-pr-review-flow.md +52 -10
  199. package/docs/internals/startup-recovery-audit.md +58 -0
  200. package/docs/mcp.md +74 -41
  201. package/docs/mission-processes.md +59 -0
  202. package/docs/multi-repo-projects.md +51 -0
  203. package/docs/platforms/windows-parity.md +70 -0
  204. package/docs/platforms/windows.md +13 -11
  205. package/docs/running-pipelines.md +14 -0
  206. package/mcp-bridge/dist/specrails-mcp.js +7186 -0
  207. package/package.json +9 -4
  208. package/server/dist/agent-chat-manager.js +537 -68
  209. package/server/dist/agent-chat-router.js +87 -6
  210. package/server/dist/agent-context-resolver.js +129 -25
  211. package/server/dist/agent-defaults.js +19 -11
  212. package/server/dist/agent-input-store.js +206 -0
  213. package/server/dist/agent-mcp-config.js +54 -15
  214. package/server/dist/agent-operator-prompt.js +255 -65
  215. package/server/dist/agent-spec-framing.js +202 -0
  216. package/server/dist/agent-steering.js +218 -0
  217. package/server/dist/agent-store.js +153 -0
  218. package/server/dist/api-not-found.js +8 -0
  219. package/server/dist/background-process-control.js +164 -0
  220. package/server/dist/background-process-service.js +61 -0
  221. package/server/dist/background-process-store.js +242 -0
  222. package/server/dist/background-windows-bootstrap.js +238 -0
  223. package/server/dist/blueprint-chat-manager.js +476 -62
  224. package/server/dist/blueprint-commit.js +21 -1
  225. package/server/dist/blueprint-draft-parser.js +122 -17
  226. package/server/dist/blueprint-generation.js +238 -0
  227. package/server/dist/blueprint-operator-prompt.js +91 -63
  228. package/server/dist/blueprint-router.js +55 -3
  229. package/server/dist/blueprint-spec-fixtures.js +85 -0
  230. package/server/dist/blueprint-spec-quality.js +57 -16
  231. package/server/dist/blueprint-store.js +92 -1
  232. package/server/dist/browser-capture-manager.js +86 -30
  233. package/server/dist/browser-playwright.js +168 -45
  234. package/server/dist/browser-viewport.js +26 -0
  235. package/server/dist/chat-manager.js +23 -15
  236. package/server/dist/chromium-archive.cjs +83 -0
  237. package/server/dist/chromium-resolver.js +72 -64
  238. package/server/dist/code-activity.js +145 -0
  239. package/server/dist/code-explorer-router.js +714 -114
  240. package/server/dist/core-compat.js +28 -1
  241. package/server/dist/core-execution.js +111 -0
  242. package/server/dist/core-package.js +4 -16
  243. package/server/dist/core-runtime.js +202 -0
  244. package/server/dist/core-update-manager.js +161 -60
  245. package/server/dist/core-update-state.js +29 -0
  246. package/server/dist/db.js +66 -35
  247. package/server/dist/delivery-evidence.js +142 -39
  248. package/server/dist/desktop-db.js +239 -43
  249. package/server/dist/desktop-router.js +109 -9
  250. package/server/dist/dev-ports.js +8 -3
  251. package/server/dist/file-provenance.js +34 -22
  252. package/server/dist/file-story-manager.js +62 -64
  253. package/server/dist/file-story.js +118 -35
  254. package/server/dist/file-summary-generator.js +46 -5
  255. package/server/dist/file-summary-manager.js +355 -106
  256. package/server/dist/framework-manager.js +98 -110
  257. package/server/dist/framework-reseed.js +76 -10
  258. package/server/dist/git-diagnostics.js +2 -2
  259. package/server/dist/headroom-manager.js +38 -3
  260. package/server/dist/host-control.js +52 -0
  261. package/server/dist/index.js +43 -2
  262. package/server/dist/interactive-job-session.js +149 -16
  263. package/server/dist/internal-api.js +48 -0
  264. package/server/dist/jira/jira-materializer.js +1 -0
  265. package/server/dist/json-tolerant.js +174 -0
  266. package/server/dist/legacy-migration.js +4 -2
  267. package/server/dist/loop-command-catalog.js +83 -21
  268. package/server/dist/loop-constants.js +6 -1
  269. package/server/dist/loop-decider.js +11 -1
  270. package/server/dist/loop-executors.js +194 -45
  271. package/server/dist/loop-factory.js +11 -4
  272. package/server/dist/loop-graph.js +90 -4
  273. package/server/dist/loop-run-manager.js +301 -69
  274. package/server/dist/loop-shell-invocation.js +21 -0
  275. package/server/dist/loop-step-idle.js +55 -0
  276. package/server/dist/loop-templates.js +31 -7
  277. package/server/dist/mcp/agent-capability.js +37 -4
  278. package/server/dist/mcp/guide.js +149 -36
  279. package/server/dist/mcp/mcp-admin-router.js +20 -4
  280. package/server/dist/mcp/mcp-server.js +89 -43
  281. package/server/dist/mcp/mcp-tiers.js +6 -5
  282. package/server/dist/mcp/mcp-token.js +3 -1
  283. package/server/dist/mcp/resources.js +5 -4
  284. package/server/dist/mcp/tools/catalog.js +4 -0
  285. package/server/dist/mcp/tools/code.js +125 -14
  286. package/server/dist/mcp/tools/context.js +153 -0
  287. package/server/dist/mcp/tools/git.js +15 -5
  288. package/server/dist/mcp/tools/jobs.js +84 -71
  289. package/server/dist/mcp/tools/loops.js +17 -6
  290. package/server/dist/mcp/tools/meta.js +74 -27
  291. package/server/dist/mcp/tools/mission.js +29 -0
  292. package/server/dist/mcp/tools/projects.js +22 -6
  293. package/server/dist/mcp/tools/rails.js +48 -11
  294. package/server/dist/mcp/tools/specs.js +86 -14
  295. package/server/dist/mcp/tools/support.js +1 -1
  296. package/server/dist/mcp/tools/types.js +117 -43
  297. package/server/dist/mcp/tools/watch.js +144 -51
  298. package/server/dist/milestone-chain-store.js +164 -0
  299. package/server/dist/milestone-chain.js +487 -0
  300. package/server/dist/milestone-progress.js +348 -0
  301. package/server/dist/mobile/mobile-devices.js +10 -7
  302. package/server/dist/mobile/mobile-gateway.js +2 -0
  303. package/server/dist/mobile/mobile-missions.js +214 -0
  304. package/server/dist/mobile/mobile-redact.js +17 -3
  305. package/server/dist/mobile/mobile-router.js +46 -4
  306. package/server/dist/mobile/mobile-ws.js +49 -4
  307. package/server/dist/multi-repo-bases.js +56 -0
  308. package/server/dist/multi-repo-checkout.js +101 -0
  309. package/server/dist/multi-repo-delivery.js +100 -0
  310. package/server/dist/multi-repo-execution-store.js +65 -0
  311. package/server/dist/multi-repo-execution.js +453 -0
  312. package/server/dist/offline-assemble.js +10 -8
  313. package/server/dist/openspec-runtime-plugin-commands.json +18 -0
  314. package/server/dist/openspec-runtime-plugin.js +120 -0
  315. package/server/dist/plugins/codex-spawn.js +49 -0
  316. package/server/dist/plugins/serena/manifest.js +2 -2
  317. package/server/dist/plugins/serena/templates/instructions.md +16 -0
  318. package/server/dist/pr-publisher.js +1 -1
  319. package/server/dist/profiles-router.js +3 -18
  320. package/server/dist/project-code-discovery.js +142 -0
  321. package/server/dist/project-git.js +19 -21
  322. package/server/dist/project-registry.js +272 -20
  323. package/server/dist/project-repositories.js +157 -0
  324. package/server/dist/project-repository-provenance.js +31 -0
  325. package/server/dist/project-router-background-processes.js +105 -0
  326. package/server/dist/project-router-git.js +75 -16
  327. package/server/dist/project-router-jobs.js +2 -126
  328. package/server/dist/project-router-loop-runs.js +72 -2
  329. package/server/dist/project-router-tickets.js +53 -8
  330. package/server/dist/project-router.js +239 -38
  331. package/server/dist/provider-detection.js +18 -6
  332. package/server/dist/provider-limit.js +64 -0
  333. package/server/dist/providers/claude-adapter.js +26 -1
  334. package/server/dist/providers/claude-background-tasks.js +18 -0
  335. package/server/dist/providers/claude-live-session.js +383 -0
  336. package/server/dist/providers/codex-adapter.js +19 -0
  337. package/server/dist/providers/codex-live-session.js +545 -0
  338. package/server/dist/providers/gemini-adapter.js +1 -0
  339. package/server/dist/providers/live-session-types.js +12 -0
  340. package/server/dist/providers/live-session.js +13 -0
  341. package/server/dist/providers/terminal-result.js +24 -0
  342. package/server/dist/queue-manager.js +2 -0
  343. package/server/dist/rail-isolated-launch.js +141 -16
  344. package/server/dist/rail-pr-decision.js +364 -99
  345. package/server/dist/rail-pr-store.js +24 -10
  346. package/server/dist/rail-worktree-release.js +34 -2
  347. package/server/dist/rail-worktrees-store.js +4 -4
  348. package/server/dist/rails-router.js +249 -55
  349. package/server/dist/repo-lock.js +92 -17
  350. package/server/dist/review-packet.js +23 -13
  351. package/server/dist/schemas/file-summary.v1.json +59 -59
  352. package/server/dist/schemas/profile.v1.json +137 -137
  353. package/server/dist/setup-manager.js +52 -8
  354. package/server/dist/shell-integration/bash-shim.bash +45 -0
  355. package/server/dist/shell-integration/fish-shim.fish +36 -0
  356. package/server/dist/shell-integration/powershell-shim.ps1 +48 -0
  357. package/server/dist/shell-integration/zsh-shim.zsh +47 -0
  358. package/server/dist/smash-runner.js +1 -0
  359. package/server/dist/spawn-lifecycle.js +44 -27
  360. package/server/dist/spec-contract-prompt.js +143 -0
  361. package/server/dist/spec-draft-parser.js +6 -1
  362. package/server/dist/spec-models.js +1 -0
  363. package/server/dist/stuck-run-detector.js +1 -0
  364. package/server/dist/terminal-manager.js +21 -1
  365. package/server/dist/terminal-shell-integration.js +4 -1
  366. package/server/dist/transient-children.js +619 -230
  367. package/server/dist/util/cli-prompt.js +78 -5
  368. package/server/dist/util/sqlite-migrations.js +20 -0
  369. package/server/dist/util/win-spawn.js +18 -3
  370. package/server/dist/util/windows-provider-shell.js +29 -0
  371. package/server/dist/verification-sentinel.js +14 -0
  372. package/server/dist/vitest-setup.js +2 -0
  373. package/server/dist/windows-job-supervisor.js +133 -0
  374. package/server/dist/workspace-manager.js +9 -30
  375. package/server/dist/worktree-manager.js +17 -2
  376. package/server/dist/worktree-overlay.js +3 -1
  377. package/client/dist/assets/ActivityFeedPage-tAQcWs1R.js +0 -1
  378. package/client/dist/assets/AgentBrowserCapture-BX8BNae5.js +0 -1
  379. package/client/dist/assets/AgentModeAnalyticsPane-BVAqCL0K.js +0 -2
  380. package/client/dist/assets/AgentModeCodePane-9XfqQc9c.js +0 -2
  381. package/client/dist/assets/AgentModeJobsPane-BCjLsPhc.js +0 -1
  382. package/client/dist/assets/AgentsPage-C8J_42sm.js +0 -87
  383. package/client/dist/assets/AnalyticsPage-BtQuyfJm.js +0 -1
  384. package/client/dist/assets/CodePage-BZ2MwdCu.js +0 -2
  385. package/client/dist/assets/DesktopAnalyticsPage-Bh_29V4g.js +0 -1
  386. package/client/dist/assets/ExportDropdown-BrtfWzEG.js +0 -1
  387. package/client/dist/assets/InteractiveJobComposer-BCZiSfzc.js +0 -19
  388. package/client/dist/assets/JobDetailModal-NAJmuYIT.js +0 -1
  389. package/client/dist/assets/JobDetailPage-fE0ZycUX.js +0 -1
  390. package/client/dist/assets/JobsPage-z5M_HVJG.js +0 -1
  391. package/client/dist/assets/LoopBuilderPage-j1FQS47c.js +0 -7
  392. package/client/dist/assets/LoopPreviewModal-DhDJjdCe.js +0 -1
  393. package/client/dist/assets/LoopsPage-Ll7zORZb.js +0 -1
  394. package/client/dist/assets/MinimizedChatsContext-Bd3OqR_S.js +0 -1
  395. package/client/dist/assets/PluginsPage-DN4kGvJt.js +0 -2
  396. package/client/dist/assets/ProjectSettingsDialog-OgsgxjzF.js +0 -1
  397. package/client/dist/assets/ReviewPacketPage-DFog4sXn.js +0 -1
  398. package/client/dist/assets/TemplatePreviewModal-L2XxqiOD.js +0 -1
  399. package/client/dist/assets/TicketDetailModal-CsF-AWZX.js +0 -14
  400. package/client/dist/assets/Trans-Bsz9_gyq.js +0 -1
  401. package/client/dist/assets/agent-BQgKv_2F.js +0 -1
  402. package/client/dist/assets/agent-C1NRDJqL.js +0 -1
  403. package/client/dist/assets/agent-CLNleiy6.js +0 -1
  404. package/client/dist/assets/agent-CmceArkQ.js +0 -1
  405. package/client/dist/assets/agent-D3UxQ3SR.js +0 -1
  406. package/client/dist/assets/agent-DB1JeE64.js +0 -1
  407. package/client/dist/assets/agent-DXkHcQ-D.js +0 -1
  408. package/client/dist/assets/agent-DjJJJ2M8.js +0 -1
  409. package/client/dist/assets/auth-D_5bQv5W.js +0 -1
  410. package/client/dist/assets/ban-E4EaZe3a.js +0 -1
  411. package/client/dist/assets/bot-DzuuuC5R.js +0 -1
  412. package/client/dist/assets/browser-BgqHDMXP.js +0 -1
  413. package/client/dist/assets/browser-CsKJtrpv.js +0 -1
  414. package/client/dist/assets/browser-Cu58EpZU.js +0 -1
  415. package/client/dist/assets/browser-CvXOJm-j.js +0 -1
  416. package/client/dist/assets/browser-DOrFh1c7.js +0 -1
  417. package/client/dist/assets/browser-DQv_Jo7x.js +0 -1
  418. package/client/dist/assets/browser-LPgxvJu2.js +0 -1
  419. package/client/dist/assets/browser-eL-3MIEI.js +0 -1
  420. package/client/dist/assets/builder-0IWtosUm.js +0 -1
  421. package/client/dist/assets/builder-Bf2N6XWn.js +0 -1
  422. package/client/dist/assets/builder-BnqaaBkT.js +0 -1
  423. package/client/dist/assets/builder-BxxfGgAO.js +0 -1
  424. package/client/dist/assets/builder-C5IIgWsq.js +0 -1
  425. package/client/dist/assets/builder-CHuahItu.js +0 -1
  426. package/client/dist/assets/builder-CjWosXL_.js +0 -1
  427. package/client/dist/assets/builder-CpCIOoKj.js +0 -1
  428. package/client/dist/assets/code-CHPkOkDS.js +0 -1
  429. package/client/dist/assets/commands-6tYQih5m.js +0 -1
  430. package/client/dist/assets/commands-BK2QinXN.js +0 -1
  431. package/client/dist/assets/commands-C2Nca76h.js +0 -1
  432. package/client/dist/assets/commands-CKdLihNX.js +0 -1
  433. package/client/dist/assets/commands-CYIBTAlc.js +0 -1
  434. package/client/dist/assets/commands-CjUtYJIM.js +0 -1
  435. package/client/dist/assets/commands-Cq_u_8y9.js +0 -1
  436. package/client/dist/assets/commands-D_ZHYQug.js +0 -1
  437. package/client/dist/assets/common-6MLbD-jM.js +0 -1
  438. package/client/dist/assets/common-BKGEu4rm.js +0 -1
  439. package/client/dist/assets/common-BNbnv8cE.js +0 -1
  440. package/client/dist/assets/common-BqGWtDbt.js +0 -1
  441. package/client/dist/assets/common-DBtTzeYm.js +0 -1
  442. package/client/dist/assets/common-DEW3Nr8n.js +0 -1
  443. package/client/dist/assets/common-DHsv9N5d.js +0 -1
  444. package/client/dist/assets/common-mkrEcW-Y.js +0 -1
  445. package/client/dist/assets/dashboard-B9GhC5ku.js +0 -1
  446. package/client/dist/assets/dashboard-CBnv6zOI.js +0 -1
  447. package/client/dist/assets/dashboard-CaP0fU87.js +0 -1
  448. package/client/dist/assets/dashboard-D4ZZ4x84.js +0 -1
  449. package/client/dist/assets/dashboard-DmuTQBX8.js +0 -1
  450. package/client/dist/assets/dashboard-DynLYj3d.js +0 -1
  451. package/client/dist/assets/dashboard-zj4gGFJ1.js +0 -1
  452. package/client/dist/assets/dialog-CdWCb02j.js +0 -45
  453. package/client/dist/assets/dist-bVqfYmby.js +0 -1
  454. package/client/dist/assets/dist-js-CWeUdf5Z.js +0 -1
  455. package/client/dist/assets/formatDistanceToNow-DQCvMgUc.js +0 -1
  456. package/client/dist/assets/getTimezoneOffsetInMilliseconds-C16cxSe1.js +0 -1
  457. package/client/dist/assets/git-refresh-wTW0rO0Z.js +0 -1
  458. package/client/dist/assets/i18n-hnX2DPmo.js +0 -2
  459. package/client/dist/assets/index-DIhMEAVw.js +0 -66
  460. package/client/dist/assets/index-DwygcU7s.css +0 -2
  461. package/client/dist/assets/mcp-B8X8wBLu.js +0 -1
  462. package/client/dist/assets/mcp-C2AojZZN.js +0 -1
  463. package/client/dist/assets/mcp-D8BT78_L.js +0 -1
  464. package/client/dist/assets/mcp-Df5RWBNk.js +0 -1
  465. package/client/dist/assets/mcp-Dj7BgMQW.js +0 -1
  466. package/client/dist/assets/mcp-Q7iT7rcz.js +0 -1
  467. package/client/dist/assets/mcp-YlvNvJbW.js +0 -1
  468. package/client/dist/assets/mcp-mKFpTSRb.js +0 -1
  469. package/client/dist/assets/narration-4Z2VUhZw.js +0 -1
  470. package/client/dist/assets/narration-BGuiEDj3.js +0 -1
  471. package/client/dist/assets/narration-BuSoqRmv.js +0 -1
  472. package/client/dist/assets/narration-CB9t29fy.js +0 -1
  473. package/client/dist/assets/narration-CU3x8FHD.js +0 -1
  474. package/client/dist/assets/narration-CWeXhfP5.js +0 -1
  475. package/client/dist/assets/narration-DRaDnhT5.js +0 -1
  476. package/client/dist/assets/narration-DtEbQgCw.js +0 -1
  477. package/client/dist/assets/nav-ApJVg3xu.js +0 -1
  478. package/client/dist/assets/nav-BKNky1Bg.js +0 -1
  479. package/client/dist/assets/nav-C6vWWKYx.js +0 -1
  480. package/client/dist/assets/nav-CXJ6Aw89.js +0 -1
  481. package/client/dist/assets/nav-CsmFBIDf.js +0 -1
  482. package/client/dist/assets/nav-DjkmQJjd.js +0 -1
  483. package/client/dist/assets/packet-B1IxaL5f.js +0 -1
  484. package/client/dist/assets/packet-Bh5kkbiB.js +0 -1
  485. package/client/dist/assets/packet-By8fqYxW.js +0 -1
  486. package/client/dist/assets/packet-CZdvA44E.js +0 -1
  487. package/client/dist/assets/packet-CrjHRQCP.js +0 -1
  488. package/client/dist/assets/packet-Dwi2pTtx.js +0 -1
  489. package/client/dist/assets/packet-_-ZIa0IR.js +0 -1
  490. package/client/dist/assets/packet-n0W6E1Hn.js +0 -1
  491. package/client/dist/assets/play-DNlrawbS.js +0 -1
  492. package/client/dist/assets/provider-capabilities-Doej9lfa.js +0 -1
  493. package/client/dist/assets/settings-Bd0x4TBa.js +0 -1
  494. package/client/dist/assets/settings-C1-7cXzC.js +0 -1
  495. package/client/dist/assets/settings-CAFaT38b.js +0 -1
  496. package/client/dist/assets/settings-Ct3v1nW2.js +0 -1
  497. package/client/dist/assets/settings-DHh7_IF5.js +0 -1
  498. package/client/dist/assets/settings-DQXAmdcv.js +0 -1
  499. package/client/dist/assets/settings-guzlhpup.js +0 -1
  500. package/client/dist/assets/settings-sZfTPMEH.js +0 -1
  501. package/client/dist/assets/setup-3jWaoVUO.js +0 -1
  502. package/client/dist/assets/setup-BjgRneb2.js +0 -1
  503. package/client/dist/assets/setup-BzFIjv-f.js +0 -1
  504. package/client/dist/assets/setup-C4VfQsxE.js +0 -1
  505. package/client/dist/assets/setup-DfUkpDbN.js +0 -1
  506. package/client/dist/assets/setup-ECe90Pmu.js +0 -1
  507. package/client/dist/assets/setup-dlqC4Jmm.js +0 -1
  508. package/client/dist/assets/setup-jwfLMiM2.js +0 -1
  509. package/client/dist/assets/spending-EC9xqio-.js +0 -1
  510. package/client/dist/assets/useDesktop-BQVt8lFd.js +0 -1
@@ -3,13 +3,15 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.atomicWrite = exports.FrameworkManager = void 0;
7
- exports.frameworkRoot = frameworkRoot;
8
- exports.readCurrentFrameworkVersion = readCurrentFrameworkVersion;
6
+ exports.atomicWrite = exports.FrameworkManager = exports.readCurrentFrameworkVersion = exports.frameworkRoot = void 0;
9
7
  const child_process_1 = require("child_process");
10
8
  const fs_1 = __importDefault(require("fs"));
11
9
  const path_1 = __importDefault(require("path"));
12
- const bundled_core_1 = require("./bundled-core");
10
+ const crypto_1 = require("crypto");
11
+ const core_runtime_1 = require("./core-runtime");
12
+ var core_runtime_2 = require("./core-runtime");
13
+ Object.defineProperty(exports, "frameworkRoot", { enumerable: true, get: function () { return core_runtime_2.frameworkRoot; } });
14
+ Object.defineProperty(exports, "readCurrentFrameworkVersion", { enumerable: true, get: function () { return core_runtime_2.readCurrentFrameworkVersion; } });
13
15
  const artifact_registry_1 = require("./artifact-registry");
14
16
  Object.defineProperty(exports, "atomicWrite", { enumerable: true, get: function () { return artifact_registry_1.atomicWrite; } });
15
17
  const semver_lite_1 = require("./semver-lite");
@@ -42,85 +44,6 @@ function realpathOrSelf(p) {
42
44
  return p;
43
45
  }
44
46
  }
45
- /** `~/.specrails/framework` — same home as the registry. */
46
- function frameworkRoot(home) {
47
- return path_1.default.join((0, artifact_registry_1.resolveHome)(home), '.specrails', 'framework');
48
- }
49
- /** Path to the `current` symlink under the framework root. */
50
- function currentLink(home) {
51
- return path_1.default.join(frameworkRoot(home), 'current');
52
- }
53
- /**
54
- * Read the framework version from the documented per-version stamp marker that
55
- * core's `install-framework` writes at `<versionDir>/.framework-stamp<providerDir>.json`
56
- * (`{ version, provider, at }`). When `current` is a real dir (Windows copy
57
- * fallback) or a junction, the version is NOT recoverable from the path basename
58
- * (`current`), so the marker inside the dir is the source of truth.
59
- *
60
- * Returns the first non-empty `version` field found among any stamp markers
61
- * directly under `dir`, or null when none is present/readable.
62
- */
63
- function readFrameworkVersionMarker(dir) {
64
- let entries;
65
- try {
66
- entries = fs_1.default.readdirSync(dir);
67
- }
68
- catch {
69
- return null;
70
- }
71
- for (const name of entries) {
72
- // `.framework-stamp.claude.json` / `.framework-stamp.codex.json` / `.gemini`.
73
- if (!name.startsWith('.framework-stamp') || !name.endsWith('.json'))
74
- continue;
75
- try {
76
- const parsed = JSON.parse(fs_1.default.readFileSync(path_1.default.join(dir, name), 'utf8'));
77
- if (typeof parsed.version === 'string') {
78
- const v = parsed.version.trim();
79
- if (v.length > 0)
80
- return v;
81
- }
82
- }
83
- catch {
84
- /* unreadable / malformed marker — try the next one */
85
- }
86
- }
87
- return null;
88
- }
89
- /**
90
- * The version `framework/current` resolves to, or null when `current` is absent
91
- * / unresolvable. POSIX: reads the basename of the symlink target (the `<version>`
92
- * dir name). Windows junction/copy fallback: `current` is a real dir (or a
93
- * junction whose `lstat` is not a POSIX symlink), so the basename is `current`
94
- * and the version is read from the documented `.framework-stamp*.json` marker
95
- * inside it instead.
96
- */
97
- function readCurrentFrameworkVersion(home) {
98
- const link = currentLink(home);
99
- try {
100
- const st = fs_1.default.lstatSync(link);
101
- if (st.isSymbolicLink()) {
102
- const target = fs_1.default.readlinkSync(link);
103
- const resolved = path_1.default.isAbsolute(target) ? target : path_1.default.resolve(frameworkRoot(home), target);
104
- const base = path_1.default.basename(resolved);
105
- // A POSIX symlink targets the `<version>` dir by name. A Windows junction
106
- // read as a symlink also yields the absolute `<version>` target, so the
107
- // basename is the version. Only when the basename is still `current`
108
- // (unresolvable) do we fall back to the stamp marker inside the dir.
109
- if (base !== 'current')
110
- return base;
111
- return readFrameworkVersionMarker(resolved);
112
- }
113
- if (st.isDirectory()) {
114
- // Windows copy fallback / non-symlink junction: `current` is a real dir
115
- // holding the materialized framework. Read the version from its marker.
116
- return readFrameworkVersionMarker(link);
117
- }
118
- return null;
119
- }
120
- catch {
121
- return null;
122
- }
123
- }
124
47
  class FrameworkManager {
125
48
  home;
126
49
  broadcast;
@@ -139,30 +62,21 @@ class FrameworkManager {
139
62
  const cli = path_1.default.join(this.coreRoot, 'dist', 'installer', 'cli.js');
140
63
  return fs_1.default.existsSync(cli) ? realpathOrSelf(cli) : null;
141
64
  }
142
- const bundled = (0, bundled_core_1.getBundledCoreCli)();
143
- return bundled ? realpathOrSelf(bundled) : null;
65
+ return (0, core_runtime_1.resolveCoreRuntime)(this.home)?.cli ?? null;
144
66
  }
145
67
  /** True when a usable core (override or bundled) is present (else methods no-op). */
146
68
  isAvailable() {
147
69
  return this.resolveCli() !== null;
148
70
  }
149
71
  /**
150
- * The version the app should materialize: the override core's version (update
151
- * channel) when a `coreRoot` is set, else the bundled core version. Null when
72
+ * Historical API name: this is the SELECTED runtime's version (including a
73
+ * managed update or newer external CLI), not necessarily the shipped bundle. Null when
152
74
  * neither is present / unreadable.
153
75
  */
154
76
  bundledVersion() {
155
- if (this.coreRoot) {
156
- try {
157
- const pkg = JSON.parse(fs_1.default.readFileSync(path_1.default.join(this.coreRoot, 'package.json'), 'utf8'));
158
- const v = pkg.version?.trim();
159
- return v && v.length > 0 ? v : null;
160
- }
161
- catch {
162
- return null;
163
- }
164
- }
165
- return (0, bundled_core_1.getBundledCoreVersion)();
77
+ return this.coreRoot
78
+ ? (0, core_runtime_1.readCoreRuntime)(this.coreRoot, 'managed')?.version ?? null
79
+ : (0, core_runtime_1.resolveCoreRuntime)(this.home)?.version ?? null;
166
80
  }
167
81
  /**
168
82
  * Materialize the framework for `version` (default: the bundled core version)
@@ -183,7 +97,7 @@ class FrameworkManager {
183
97
  if (!ver) {
184
98
  return { ran: false, version: null, providers: [], errors: [] };
185
99
  }
186
- const fwDir = frameworkRoot(this.home);
100
+ const fwDir = (0, core_runtime_1.frameworkRoot)(this.home);
187
101
  fs_1.default.mkdirSync(fwDir, { recursive: true });
188
102
  const errors = [];
189
103
  const done = [];
@@ -233,16 +147,79 @@ class FrameworkManager {
233
147
  const cli = this.resolveCli();
234
148
  if (!cli)
235
149
  return { ok: false, detail: 'no usable core CLI to run swap-current with' };
236
- if (readCurrentFrameworkVersion(this.home) === version)
150
+ if ((0, core_runtime_1.readCurrentFrameworkVersion)(this.home) === version)
237
151
  return { ok: true, detail: null };
238
- const fwDir = frameworkRoot(this.home);
152
+ const fwDir = (0, core_runtime_1.frameworkRoot)(this.home);
153
+ const current = path_1.default.join(fwDir, 'current');
154
+ const previousVersion = (0, core_runtime_1.readCurrentFrameworkVersion)(this.home);
155
+ let backup = null;
156
+ if (previousVersion) {
157
+ backup = path_1.default.join(fwDir, `.current-recovery-${(0, crypto_1.randomUUID)()}`);
158
+ try {
159
+ if (fs_1.default.lstatSync(current).isSymbolicLink()) {
160
+ fs_1.default.symlinkSync(fs_1.default.realpathSync(current), backup, process.platform === 'win32' ? 'junction' : 'dir');
161
+ }
162
+ else {
163
+ fs_1.default.cpSync(current, backup, {
164
+ recursive: true, dereference: false, verbatimSymlinks: true,
165
+ // Node 22's native recursive-copy fast path mishandles Unicode on
166
+ // Windows (nodejs/node#61878). Use JS traversal into the fresh backup
167
+ // so neither native directory copy nor file replacement is used.
168
+ filter: () => true, mode: fs_1.default.constants.COPYFILE_FICLONE,
169
+ });
170
+ }
171
+ }
172
+ catch (error) {
173
+ return { ok: false, detail: `Could not preserve the active framework before updating: ${error instanceof Error ? error.message : String(error)}` };
174
+ }
175
+ }
176
+ const failed = (detail) => {
177
+ if (!backup)
178
+ return { ok: false, detail };
179
+ const actualVersion = (0, core_runtime_1.readCurrentFrameworkVersion)(this.home);
180
+ if (actualVersion === previousVersion) {
181
+ try {
182
+ fs_1.default.rmSync(backup, { recursive: true, force: true });
183
+ }
184
+ catch { /* safe recovery copy */ }
185
+ return { ok: false, detail };
186
+ }
187
+ // Never overwrite another operation's published version. Preserve the
188
+ // recovery copy for diagnosis instead of guessing which install owns it.
189
+ if (actualVersion !== null && actualVersion !== version)
190
+ return { ok: false, detail: `${detail}; recovery retained at ${backup} (current changed concurrently)` };
191
+ const failedCurrent = path_1.default.join(fwDir, `.failed-current-${(0, crypto_1.randomUUID)()}`);
192
+ try {
193
+ try {
194
+ fs_1.default.lstatSync(current);
195
+ fs_1.default.renameSync(current, failedCurrent);
196
+ }
197
+ catch (error) {
198
+ if (error.code !== 'ENOENT')
199
+ throw error;
200
+ }
201
+ fs_1.default.renameSync(backup, current);
202
+ if ((0, core_runtime_1.readCurrentFrameworkVersion)(this.home) !== previousVersion)
203
+ throw new Error('restored framework identity did not match');
204
+ return { ok: false, detail: `${detail}; previous framework ${previousVersion} restored` };
205
+ }
206
+ catch (error) {
207
+ return { ok: false, detail: `${detail}; recovery retained at ${backup}: ${error instanceof Error ? error.message : String(error)}` };
208
+ }
209
+ };
239
210
  const res = (0, child_process_1.spawnSync)(nodeInterpreter(), [cli, 'swap-current', '--framework-dir', fwDir, '--version', version], { env: this.childEnv(), encoding: 'utf-8', timeout: 120_000 });
240
211
  if (res.error || (res.status ?? 1) !== 0) {
241
212
  const detail = (res.error?.message ?? `${res.stderr || res.stdout || ''}`.trim()) || `exit ${res.status ?? 'unknown'}`;
242
- return { ok: false, detail };
213
+ return failed(detail);
243
214
  }
244
- if (readCurrentFrameworkVersion(this.home) !== version) {
245
- return { ok: false, detail: `swap-current exited 0 but current still resolves to ${readCurrentFrameworkVersion(this.home) ?? 'nothing'}` };
215
+ if ((0, core_runtime_1.readCurrentFrameworkVersion)(this.home) !== version) {
216
+ return failed(`swap-current exited 0 but current still resolves to ${(0, core_runtime_1.readCurrentFrameworkVersion)(this.home) ?? 'nothing'}`);
217
+ }
218
+ if (backup) {
219
+ try {
220
+ fs_1.default.rmSync(backup, { recursive: true, force: true });
221
+ }
222
+ catch { /* recovery copy is safe to retain */ }
246
223
  }
247
224
  return { ok: true, detail: null };
248
225
  }
@@ -253,12 +230,23 @@ class FrameworkManager {
253
230
  * no bundled core is present. Returns the resulting current version (or null).
254
231
  */
255
232
  versionCheck(providers = ['claude']) {
256
- const bundled = this.bundledVersion();
233
+ // Keep an already activated framework even if its old updater discarded the
234
+ // corresponding package. Runtime consumers report the repair requirement.
235
+ let bundled;
236
+ try {
237
+ bundled = this.bundledVersion();
238
+ }
239
+ catch {
240
+ return { swapped: false, version: (0, core_runtime_1.readCurrentFrameworkVersion)(this.home) };
241
+ }
257
242
  if (!this.resolveCli() || !bundled) {
258
- return { swapped: false, version: readCurrentFrameworkVersion(this.home) };
243
+ return { swapped: false, version: (0, core_runtime_1.readCurrentFrameworkVersion)(this.home) };
259
244
  }
260
- const current = readCurrentFrameworkVersion(this.home);
245
+ const current = (0, core_runtime_1.readCurrentFrameworkVersion)(this.home);
261
246
  if (current === bundled) {
247
+ const mat = this.materialize(bundled, providers);
248
+ if (mat.errors.length)
249
+ this.broadcast?.({ type: 'framework.update_failed', version: bundled, errors: mat.errors });
262
250
  return { swapped: false, version: current };
263
251
  }
264
252
  // Anti-downgrade guard: NEVER swap `current` down to the bundled version when
@@ -318,7 +306,7 @@ class FrameworkManager {
318
306
  catch {
319
307
  /* broadcast is best-effort */
320
308
  }
321
- return { swapped: false, version: readCurrentFrameworkVersion(this.home) };
309
+ return { swapped: false, version: (0, core_runtime_1.readCurrentFrameworkVersion)(this.home) };
322
310
  }
323
311
  /**
324
312
  * Assemble a project workspace by SYMLINKING the materialized framework into
@@ -332,7 +320,7 @@ class FrameworkManager {
332
320
  const ver = input.version ?? this.bundledVersion();
333
321
  if (!ver)
334
322
  return { ran: false };
335
- const fwDir = frameworkRoot(this.home);
323
+ const fwDir = (0, core_runtime_1.frameworkRoot)(this.home);
336
324
  const res = (0, child_process_1.spawnSync)(nodeInterpreter(), [
337
325
  cli,
338
326
  'assemble',
@@ -362,7 +350,7 @@ class FrameworkManager {
362
350
  // Point the core CLI's scriptDir at the package whose framework we are
363
351
  // materializing so its template/command sources resolve from there: the
364
352
  // OVERRIDE core (update channel) when set, else the bundled package.
365
- const coreRoot = this.coreRoot ?? process.env.SPECRAILS_BUNDLED_CORE_PATH;
353
+ const coreRoot = this.coreRoot ?? (0, core_runtime_1.resolveCoreRuntime)(this.home)?.root;
366
354
  if (coreRoot)
367
355
  env.SPECRAILS_CORE_SCRIPT_DIR = coreRoot;
368
356
  return env;
@@ -46,6 +46,7 @@ var __importStar = (this && this.__importStar) || (function () {
46
46
  };
47
47
  })();
48
48
  Object.defineProperty(exports, "__esModule", { value: true });
49
+ exports.assertWorkspaceCoreReady = void 0;
49
50
  exports.isFrameworkAutoswapEnabled = isFrameworkAutoswapEnabled;
50
51
  exports.readWorkspaceFrameworkVersion = readWorkspaceFrameworkVersion;
51
52
  exports.reseedStaleWorkspaces = reseedStaleWorkspaces;
@@ -55,6 +56,10 @@ const providers_1 = require("./providers");
55
56
  const workspace_manager_1 = require("./workspace-manager");
56
57
  const workspace_resolution_1 = require("./workspace-resolution");
57
58
  const offline_assemble_1 = require("./offline-assemble");
59
+ const core_runtime_1 = require("./core-runtime");
60
+ const core_update_state_1 = require("./core-update-state");
61
+ var core_update_state_2 = require("./core-update-state");
62
+ Object.defineProperty(exports, "assertWorkspaceCoreReady", { enumerable: true, get: function () { return core_update_state_2.assertWorkspaceCoreReady; } });
58
63
  function isFrameworkAutoswapEnabled() {
59
64
  const v = (process.env.SPECRAILS_FRAMEWORK_AUTOSWAP ?? '').toLowerCase();
60
65
  return v !== 'false' && v !== '0' && v !== 'off';
@@ -84,15 +89,37 @@ async function reseedStaleWorkspaces(projects, currentVersion, io) {
84
89
  const results = [];
85
90
  if (!currentVersion)
86
91
  return results;
92
+ // Mark every affected workspace before yielding for the first install. A
93
+ // later project must not launch against mixed templates while earlier ones
94
+ // are still being refreshed.
95
+ const pendingErrors = new Map();
96
+ for (const project of projects) {
97
+ const workspace = (0, workspace_manager_1.workspacePathFor)(project.slug);
98
+ if (!(0, workspace_resolution_1.isWorkspacePopulated)(workspace))
99
+ continue;
100
+ if (readWorkspaceFrameworkVersion(project.slug) === currentVersion && !fs.existsSync((0, core_update_state_1.coreUpdatePendingPath)(workspace)))
101
+ continue;
102
+ try {
103
+ fs.mkdirSync(path.dirname((0, core_update_state_1.coreUpdatePendingPath)(workspace)), { recursive: true });
104
+ fs.writeFileSync((0, core_update_state_1.coreUpdatePendingPath)(workspace), JSON.stringify({ version: currentVersion, projectId: project.id }), { mode: 0o600 });
105
+ }
106
+ catch (error) {
107
+ pendingErrors.set(project.id, `Could not mark the workspace for Core refresh: ${String(error)}`);
108
+ }
109
+ }
87
110
  const assemble = io?.assemble
88
111
  ?? (async (p, providers) => {
89
- await (0, offline_assemble_1.assembleProjectOffline)({
112
+ const results = await (0, offline_assemble_1.assembleProjectOffline)({
90
113
  projectPath: p.path,
91
114
  slug: p.slug,
92
115
  desktopProjectId: p.id,
93
116
  providers,
94
117
  continueOnError: true,
118
+ preserveExistingConfig: true,
95
119
  });
120
+ const failed = results.filter(result => !result.ok);
121
+ if (failed.length)
122
+ throw new Error(failed.map(result => `${result.provider}: ${result.error}`).join('; '));
96
123
  });
97
124
  for (const project of projects) {
98
125
  const workspace = (0, workspace_manager_1.workspacePathFor)(project.slug);
@@ -101,7 +128,7 @@ async function reseedStaleWorkspaces(projects, currentVersion, io) {
101
128
  continue;
102
129
  }
103
130
  const recorded = readWorkspaceFrameworkVersion(project.slug);
104
- if (recorded === currentVersion) {
131
+ if (recorded === currentVersion && !fs.existsSync((0, core_update_state_1.coreUpdatePendingPath)(workspace))) {
105
132
  results.push({ projectId: project.id, reseeded: false, skippedReason: 'up-to-date' });
106
133
  continue;
107
134
  }
@@ -118,18 +145,27 @@ async function reseedStaleWorkspaces(projects, currentVersion, io) {
118
145
  }
119
146
  catch { /* absent */ }
120
147
  try {
148
+ if (pendingErrors.has(project.id))
149
+ throw new Error(pendingErrors.get(project.id));
121
150
  await assemble(project, providers);
122
- if (mcpBefore !== null) {
123
- let mcpAfter = null;
124
- try {
125
- mcpAfter = fs.readFileSync(mcpPath, 'utf-8');
151
+ if (readWorkspaceFrameworkVersion(project.slug) !== currentVersion)
152
+ throw new Error('Core assembly did not publish the expected workspace version.');
153
+ // Published Core 5 installations before the execution contract remain
154
+ // valid. Require the helper only when the selected package declares it.
155
+ if (!io?.assemble) {
156
+ const runtime = (0, core_runtime_1.resolveCoreRuntime)();
157
+ if (runtime) {
158
+ const contractPath = path.join(runtime.root, 'integration-contract.json');
159
+ let required;
160
+ try {
161
+ required = JSON.parse(fs.readFileSync(contractPath, 'utf8')).execution?.runtime;
162
+ }
163
+ catch { /* legacy contract */ }
164
+ if (typeof required === 'string' && required && !fs.existsSync(path.join(workspace, required)))
165
+ throw new Error('Core assembly did not install its declared execution runtime.');
126
166
  }
127
- catch { /* deleted by assemble */ }
128
- if (mcpAfter !== mcpBefore)
129
- fs.writeFileSync(mcpPath, mcpBefore, 'utf-8');
130
167
  }
131
168
  results.push({ projectId: project.id, reseeded: true });
132
- console.log(`[framework-reseed] ${project.slug}: ${recorded ?? '(none)'} → ${currentVersion}`);
133
169
  }
134
170
  catch (err) {
135
171
  results.push({
@@ -139,6 +175,36 @@ async function reseedStaleWorkspaces(projects, currentVersion, io) {
139
175
  });
140
176
  console.warn(`[framework-reseed] ${project.slug} failed (non-fatal):`, err);
141
177
  }
178
+ finally {
179
+ if (mcpBefore !== null) {
180
+ let mcpAfter = null;
181
+ try {
182
+ mcpAfter = fs.readFileSync(mcpPath, 'utf-8');
183
+ }
184
+ catch { /* deleted by assemble */ }
185
+ if (mcpAfter !== mcpBefore) {
186
+ try {
187
+ fs.writeFileSync(mcpPath, mcpBefore, 'utf-8');
188
+ }
189
+ catch (error) {
190
+ const result = results[results.length - 1];
191
+ result.reseeded = false;
192
+ result.error = `Could not restore the project's MCP configuration: ${String(error)}`;
193
+ }
194
+ }
195
+ }
196
+ const result = results[results.length - 1];
197
+ if (result.reseeded) {
198
+ try {
199
+ fs.rmSync((0, core_update_state_1.coreUpdatePendingPath)(workspace), { force: true });
200
+ console.log(`[framework-reseed] ${project.slug}: ${recorded ?? '(none)'} → ${currentVersion}`);
201
+ }
202
+ catch (error) {
203
+ result.reseeded = false;
204
+ result.error = `Could not finalize the Core refresh: ${String(error)}`;
205
+ }
206
+ }
207
+ }
142
208
  }
143
209
  return results;
144
210
  }
@@ -17,7 +17,7 @@ const win_spawn_1 = require("./util/win-spawn");
17
17
  * SAFETY: the command list is a FIXED ALLOWLIST — the caller only picks an
18
18
  * action name, never arguments, so no flag/ref/path can be smuggled in. Every
19
19
  * command is read-only (no push/commit/checkout/pr-create). git runs with the
20
- * hardened `GIT_EXEC_ENV` (hostile-repo config stripped, prompts disabled); gh
20
+ * hardened `gitExecEnv()` (hostile-repo config stripped, prompts disabled); gh
21
21
  * runs with the normal env so it finds the user's `~/.config/gh` auth and the
22
22
  * bundled `gh` on PATH.
23
23
  */
@@ -43,7 +43,7 @@ const OUTPUT_CAP = 8_000;
43
43
  const defaultExec = (cmd, args, cwd) => new Promise((resolve) => {
44
44
  // git: hardened, cwd-pinned env. gh: user env (needs ~/.config/gh auth) with
45
45
  // the Windows shell-critical vars guaranteed present.
46
- const env = cmd === 'gh' ? (0, win_spawn_1.windowsSpawnEnv)() : file_provenance_1.GIT_EXEC_ENV;
46
+ const env = cmd === 'gh' ? (0, win_spawn_1.windowsSpawnEnv)() : (0, file_provenance_1.gitExecEnv)();
47
47
  (0, child_process_1.execFile)(cmd, args, { cwd, env, timeout: 15_000, maxBuffer: 8 * 1024 * 1024, windowsHide: true }, (err, stdout, stderr) => {
48
48
  const code = err && typeof err.code === 'number'
49
49
  ? err.code
@@ -3,7 +3,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.HeadroomManager = exports.HEADROOM_ACTIVATION_SOURCE = exports.HEADROOM_ACTIVATION_SCHEMA_VERSION = exports.HEADROOM_MANAGED_PYTHON_VERSION = exports.HEADROOM_RELAY_PATH = void 0;
6
+ exports.HeadroomManager = exports.HEADROOM_ACTIVATION_SOURCE = exports.HEADROOM_ACTIVATION_SCHEMA_VERSION = exports.HEADROOM_MANAGED_PYTHON_VERSION = exports.HEADROOM_RELAY_PATH = exports.PROXY_START_TIMEOUT_MS = void 0;
7
+ exports.getProxyStartTimeoutMs = getProxyStartTimeoutMs;
7
8
  exports.getHeadroomManagedInstallPlan = getHeadroomManagedInstallPlan;
8
9
  exports.parseWindowsOwnershipSnapshot = parseWindowsOwnershipSnapshot;
9
10
  exports.windowsSnapshotHasOwnedListener = windowsSnapshotHasOwnedListener;
@@ -22,7 +23,27 @@ const win_spawn_1 = require("./util/win-spawn");
22
23
  const STATE_KEY = 'plugins.headroom.state';
23
24
  const DEFAULT_PORT = 8787;
24
25
  const PROXY_HEALTH_REQUEST_TIMEOUT_MS = 750;
26
+ /**
27
+ * How long a freshly spawned proxy gets to answer `/livez` before activation
28
+ * is declared failed. The proxy is a Python tool run through uv: a COLD start
29
+ * (first run after install, or a wiped bytecode cache) compiles ~3 000
30
+ * modules and only prints its banner ~4 s in, answering `/livez` after ~7.5 s
31
+ * on an M-series Mac — the previous 6 s budget killed it every time and left
32
+ * `proxyTail` empty, so the diagnostics said nothing. Warm starts take ~3.7 s.
33
+ * A child that EXITS is failed immediately regardless of this budget.
34
+ */
35
+ exports.PROXY_START_TIMEOUT_MS = 30_000;
36
+ const PROXY_START_TIMEOUT_ENV = 'SPECRAILS_HEADROOM_START_TIMEOUT_MS';
25
37
  const RELAY_CONNECT_TIMEOUT_MS = 3_000;
38
+ /** Effective proxy startup budget: the env override when it is a positive
39
+ * integer number of milliseconds, else the default. */
40
+ function getProxyStartTimeoutMs(env = process.env) {
41
+ const raw = env[PROXY_START_TIMEOUT_ENV];
42
+ if (raw === undefined || raw.trim() === '')
43
+ return exports.PROXY_START_TIMEOUT_MS;
44
+ const parsed = Number(raw);
45
+ return Number.isInteger(parsed) && parsed > 0 ? parsed : exports.PROXY_START_TIMEOUT_MS;
46
+ }
26
47
  exports.HEADROOM_RELAY_PATH = '/_specrails/headroom';
27
48
  exports.HEADROOM_MANAGED_PYTHON_VERSION = '3.12';
28
49
  exports.HEADROOM_ACTIVATION_SCHEMA_VERSION = 1;
@@ -1437,6 +1458,9 @@ class HeadroomManager {
1437
1458
  HEADROOM_LEARN: state.learning.enabled ? '1' : '0',
1438
1459
  HEADROOM_SAVINGS_PROFILE: process.env.HEADROOM_SAVINGS_PROFILE ?? 'agent-90',
1439
1460
  HEADROOM_MODE: process.env.HEADROOM_MODE ?? 'token',
1461
+ // stdout is a pipe here, so Python block-buffers it: without this the
1462
+ // banner (and any traceback) never reaches `proxyTail` before a kill.
1463
+ PYTHONUNBUFFERED: '1',
1440
1464
  },
1441
1465
  stdio: ['ignore', 'pipe', 'pipe'],
1442
1466
  });
@@ -1472,13 +1496,24 @@ class HeadroomManager {
1472
1496
  // fallback for unusual ChildProcess implementations and stdio teardown.
1473
1497
  child.on('exit', invalidateOwnedProxy);
1474
1498
  child.on('close', invalidateOwnedProxy);
1475
- const healthy = await this.waitForProxyHealthy(state.port, 6000, () => this.isProxyGenerationCurrent(generation));
1499
+ const startTimeoutMs = getProxyStartTimeoutMs();
1500
+ const childAlive = () => child.exitCode == null && child.signalCode == null;
1501
+ const healthy = await this.waitForProxyHealthy(state.port, startTimeoutMs,
1502
+ // A dead child can never become healthy — stop waiting the moment it
1503
+ // exits so a crash surfaces at once instead of after the whole budget.
1504
+ () => this.isProxyGenerationCurrent(generation) && childAlive());
1476
1505
  if (!this.isProxyGenerationCurrent(generation)) {
1477
1506
  await this.discardProxyChild(child);
1478
1507
  return this.cancelledProxyStart();
1479
1508
  }
1480
1509
  if (!healthy) {
1481
- const failure = classifyProxyFailure(this.proxyTail, command);
1510
+ // Prefer the process's own words; otherwise say precisely which of the two
1511
+ // things happened so "did not become healthy" is never an empty box.
1512
+ const tail = this.proxyTail.trim();
1513
+ const detail = tail || (childAlive()
1514
+ ? `The Headroom proxy did not answer /livez within ${Math.round(startTimeoutMs / 1000)} s.`
1515
+ : `The Headroom proxy exited during startup (${child.signalCode ?? `exit code ${child.exitCode}`}).`);
1516
+ const failure = classifyProxyFailure(detail, command);
1482
1517
  await this.discardProxyChild(child);
1483
1518
  this.updatePersisted({ lastIssue: failure });
1484
1519
  this.emit('failed', failure.title);
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.consumeHostControlToken = consumeHostControlToken;
4
+ exports.createHostControlRouter = createHostControlRouter;
5
+ const express_1 = require("express");
6
+ const auth_1 = require("./auth");
7
+ const TOKEN_ENV = 'SPECRAILS_HOST_CONTROL_TOKEN';
8
+ /** Consume the host-only capability before provider/terminal environments are copied. */
9
+ function consumeHostControlToken(env = process.env) {
10
+ let token;
11
+ for (const key of Object.keys(env)) {
12
+ if (key.toUpperCase() !== TOKEN_ENV)
13
+ continue;
14
+ token ??= env[key];
15
+ delete env[key];
16
+ }
17
+ return token && /^[a-zA-Z0-9_-]{32,256}$/.test(token) ? token : undefined;
18
+ }
19
+ /** Private host control; deliberately independent of the renderer's desktop token. */
20
+ function createHostControlRouter(options) {
21
+ const router = (0, express_1.Router)();
22
+ let requested = false;
23
+ router.post('/shutdown', (req, res) => {
24
+ if (!options.token) {
25
+ res.status(404).json({ error: 'Not found' });
26
+ return;
27
+ }
28
+ const supplied = req.headers['x-specrails-host-token'];
29
+ if (!(0, auth_1.isLoopbackAddress)(req.socket.remoteAddress) || req.headers.origin !== undefined
30
+ || typeof supplied !== 'string' || !(0, auth_1.safeEqual)(supplied, options.token)) {
31
+ res.status(403).json({ error: 'Forbidden' });
32
+ return;
33
+ }
34
+ if (!requested) {
35
+ // The host must receive the owned PID before shutdown closes all sockets.
36
+ res.once('finish', () => {
37
+ if (requested)
38
+ return;
39
+ requested = true;
40
+ setImmediate(() => {
41
+ void Promise.resolve().then(options.onShutdown).catch(() => {
42
+ // No credentials or environment values in this diagnostic. The host
43
+ // retains its bounded, identity-checked force-stop fallback.
44
+ console.error('[host-control] graceful shutdown failed');
45
+ });
46
+ });
47
+ });
48
+ }
49
+ res.status(202).json({ ok: true, pid: options.pid ?? process.pid });
50
+ });
51
+ return router;
52
+ }