specrails-desktop 2.35.0 → 2.42.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (509) 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-BPAzDICW.js → DocsDialog-Dj6mt4ga.js} +2 -2
  12. package/client/dist/assets/{DocsPage-Dqo4Hn6G.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-CcZMcEN5.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 +90 -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 +538 -67
  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 +147 -16
  214. package/server/dist/agent-operator-prompt.js +259 -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 +131 -9
  250. package/server/dist/dev-ports.js +8 -3
  251. package/server/dist/external-mcp.js +370 -0
  252. package/server/dist/file-provenance.js +19 -12
  253. package/server/dist/file-story-manager.js +62 -64
  254. package/server/dist/file-story.js +118 -35
  255. package/server/dist/file-summary-generator.js +46 -5
  256. package/server/dist/file-summary-manager.js +355 -106
  257. package/server/dist/framework-manager.js +98 -110
  258. package/server/dist/framework-reseed.js +76 -10
  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/profiles-router.js +3 -18
  319. package/server/dist/project-code-discovery.js +142 -0
  320. package/server/dist/project-git.js +18 -20
  321. package/server/dist/project-registry.js +272 -20
  322. package/server/dist/project-repositories.js +157 -0
  323. package/server/dist/project-repository-provenance.js +31 -0
  324. package/server/dist/project-router-background-processes.js +105 -0
  325. package/server/dist/project-router-git.js +75 -16
  326. package/server/dist/project-router-jobs.js +2 -126
  327. package/server/dist/project-router-loop-runs.js +72 -2
  328. package/server/dist/project-router-tickets.js +53 -8
  329. package/server/dist/project-router.js +239 -38
  330. package/server/dist/provider-detection.js +18 -6
  331. package/server/dist/provider-limit.js +64 -0
  332. package/server/dist/providers/claude-adapter.js +26 -1
  333. package/server/dist/providers/claude-background-tasks.js +18 -0
  334. package/server/dist/providers/claude-live-session.js +383 -0
  335. package/server/dist/providers/codex-adapter.js +19 -0
  336. package/server/dist/providers/codex-live-session.js +545 -0
  337. package/server/dist/providers/gemini-adapter.js +1 -0
  338. package/server/dist/providers/live-session-types.js +12 -0
  339. package/server/dist/providers/live-session.js +13 -0
  340. package/server/dist/providers/terminal-result.js +24 -0
  341. package/server/dist/queue-manager.js +2 -0
  342. package/server/dist/rail-isolated-launch.js +141 -16
  343. package/server/dist/rail-pr-decision.js +364 -99
  344. package/server/dist/rail-pr-store.js +24 -10
  345. package/server/dist/rail-worktree-release.js +34 -2
  346. package/server/dist/rail-worktrees-store.js +4 -4
  347. package/server/dist/rails-router.js +249 -55
  348. package/server/dist/repo-lock.js +92 -17
  349. package/server/dist/review-packet.js +23 -13
  350. package/server/dist/schemas/file-summary.v1.json +59 -59
  351. package/server/dist/schemas/profile.v1.json +137 -137
  352. package/server/dist/setup-manager.js +45 -7
  353. package/server/dist/shell-integration/bash-shim.bash +45 -0
  354. package/server/dist/shell-integration/fish-shim.fish +36 -0
  355. package/server/dist/shell-integration/powershell-shim.ps1 +48 -0
  356. package/server/dist/shell-integration/zsh-shim.zsh +47 -0
  357. package/server/dist/smash-runner.js +1 -0
  358. package/server/dist/spawn-lifecycle.js +44 -27
  359. package/server/dist/spec-contract-prompt.js +143 -0
  360. package/server/dist/spec-draft-parser.js +6 -1
  361. package/server/dist/spec-models.js +1 -0
  362. package/server/dist/stuck-run-detector.js +1 -0
  363. package/server/dist/terminal-manager.js +1 -1
  364. package/server/dist/terminal-shell-integration.js +4 -1
  365. package/server/dist/transient-children.js +619 -230
  366. package/server/dist/util/cli-prompt.js +78 -5
  367. package/server/dist/util/sqlite-migrations.js +20 -0
  368. package/server/dist/util/win-spawn.js +18 -3
  369. package/server/dist/util/windows-provider-shell.js +29 -0
  370. package/server/dist/verification-sentinel.js +14 -0
  371. package/server/dist/vitest-setup.js +2 -0
  372. package/server/dist/windows-job-supervisor.js +133 -0
  373. package/server/dist/workspace-manager.js +9 -30
  374. package/server/dist/worktree-manager.js +17 -2
  375. package/server/dist/worktree-overlay.js +3 -1
  376. package/client/dist/assets/ActivityFeedPage-LCBJ5WiD.js +0 -1
  377. package/client/dist/assets/AgentBrowserCapture-B-xQpyZr.js +0 -1
  378. package/client/dist/assets/AgentModeAnalyticsPane-BI1Vcf3T.js +0 -2
  379. package/client/dist/assets/AgentModeCodePane-BTRwCl9h.js +0 -2
  380. package/client/dist/assets/AgentModeJobsPane-VzeBJLqP.js +0 -1
  381. package/client/dist/assets/AgentsPage-CJj1K24O.js +0 -87
  382. package/client/dist/assets/AnalyticsPage-CQpV5_fX.js +0 -1
  383. package/client/dist/assets/CodePage-9-tFh0jB.js +0 -2
  384. package/client/dist/assets/DesktopAnalyticsPage-BcZ0_O_C.js +0 -1
  385. package/client/dist/assets/ExportDropdown-BrtfWzEG.js +0 -1
  386. package/client/dist/assets/InteractiveJobComposer-D36fZWai.js +0 -19
  387. package/client/dist/assets/JobDetailModal-BxsktbkQ.js +0 -1
  388. package/client/dist/assets/JobDetailPage-WWyCy6Sp.js +0 -1
  389. package/client/dist/assets/JobsPage-CEbM7uxU.js +0 -1
  390. package/client/dist/assets/LoopBuilderPage-CYsSN8io.js +0 -7
  391. package/client/dist/assets/LoopPreviewModal-BJhgX5wY.js +0 -1
  392. package/client/dist/assets/LoopsPage-DB4OqMot.js +0 -1
  393. package/client/dist/assets/MinimizedChatsContext-CcjBHMHd.js +0 -1
  394. package/client/dist/assets/PluginsPage-CR8K4nM_.js +0 -2
  395. package/client/dist/assets/ProjectSettingsDialog-C45-qhF4.js +0 -1
  396. package/client/dist/assets/ReviewPacketPage-Cz_QQeC3.js +0 -1
  397. package/client/dist/assets/TemplatePreviewModal-wFBd2vQP.js +0 -1
  398. package/client/dist/assets/TicketDetailModal-BvZoyDAL.js +0 -14
  399. package/client/dist/assets/Trans-DXZ-BLXb.js +0 -1
  400. package/client/dist/assets/agent-BQgKv_2F.js +0 -1
  401. package/client/dist/assets/agent-C1NRDJqL.js +0 -1
  402. package/client/dist/assets/agent-CLNleiy6.js +0 -1
  403. package/client/dist/assets/agent-CmceArkQ.js +0 -1
  404. package/client/dist/assets/agent-D3UxQ3SR.js +0 -1
  405. package/client/dist/assets/agent-DB1JeE64.js +0 -1
  406. package/client/dist/assets/agent-DXkHcQ-D.js +0 -1
  407. package/client/dist/assets/agent-DjJJJ2M8.js +0 -1
  408. package/client/dist/assets/auth-D_5bQv5W.js +0 -1
  409. package/client/dist/assets/ban-E4EaZe3a.js +0 -1
  410. package/client/dist/assets/bot-DzuuuC5R.js +0 -1
  411. package/client/dist/assets/browser-BgqHDMXP.js +0 -1
  412. package/client/dist/assets/browser-CsKJtrpv.js +0 -1
  413. package/client/dist/assets/browser-Cu58EpZU.js +0 -1
  414. package/client/dist/assets/browser-CvXOJm-j.js +0 -1
  415. package/client/dist/assets/browser-DOrFh1c7.js +0 -1
  416. package/client/dist/assets/browser-DQv_Jo7x.js +0 -1
  417. package/client/dist/assets/browser-LPgxvJu2.js +0 -1
  418. package/client/dist/assets/browser-eL-3MIEI.js +0 -1
  419. package/client/dist/assets/builder-0IWtosUm.js +0 -1
  420. package/client/dist/assets/builder-Bf2N6XWn.js +0 -1
  421. package/client/dist/assets/builder-BnqaaBkT.js +0 -1
  422. package/client/dist/assets/builder-BxxfGgAO.js +0 -1
  423. package/client/dist/assets/builder-C5IIgWsq.js +0 -1
  424. package/client/dist/assets/builder-CHuahItu.js +0 -1
  425. package/client/dist/assets/builder-CjWosXL_.js +0 -1
  426. package/client/dist/assets/builder-CpCIOoKj.js +0 -1
  427. package/client/dist/assets/code-CHPkOkDS.js +0 -1
  428. package/client/dist/assets/commands-6tYQih5m.js +0 -1
  429. package/client/dist/assets/commands-BK2QinXN.js +0 -1
  430. package/client/dist/assets/commands-C2Nca76h.js +0 -1
  431. package/client/dist/assets/commands-CKdLihNX.js +0 -1
  432. package/client/dist/assets/commands-CYIBTAlc.js +0 -1
  433. package/client/dist/assets/commands-CjUtYJIM.js +0 -1
  434. package/client/dist/assets/commands-Cq_u_8y9.js +0 -1
  435. package/client/dist/assets/commands-D_ZHYQug.js +0 -1
  436. package/client/dist/assets/common-6MLbD-jM.js +0 -1
  437. package/client/dist/assets/common-BKGEu4rm.js +0 -1
  438. package/client/dist/assets/common-BNbnv8cE.js +0 -1
  439. package/client/dist/assets/common-BqGWtDbt.js +0 -1
  440. package/client/dist/assets/common-DBtTzeYm.js +0 -1
  441. package/client/dist/assets/common-DEW3Nr8n.js +0 -1
  442. package/client/dist/assets/common-DHsv9N5d.js +0 -1
  443. package/client/dist/assets/common-mkrEcW-Y.js +0 -1
  444. package/client/dist/assets/dashboard-B9GhC5ku.js +0 -1
  445. package/client/dist/assets/dashboard-CBnv6zOI.js +0 -1
  446. package/client/dist/assets/dashboard-CaP0fU87.js +0 -1
  447. package/client/dist/assets/dashboard-D4ZZ4x84.js +0 -1
  448. package/client/dist/assets/dashboard-DmuTQBX8.js +0 -1
  449. package/client/dist/assets/dashboard-DynLYj3d.js +0 -1
  450. package/client/dist/assets/dashboard-zj4gGFJ1.js +0 -1
  451. package/client/dist/assets/dialog-CdWCb02j.js +0 -45
  452. package/client/dist/assets/dist-bVqfYmby.js +0 -1
  453. package/client/dist/assets/dist-js-CWeUdf5Z.js +0 -1
  454. package/client/dist/assets/formatDistanceToNow-C0ImhVKp.js +0 -1
  455. package/client/dist/assets/getTimezoneOffsetInMilliseconds-owhDdTXs.js +0 -1
  456. package/client/dist/assets/git-refresh-wTW0rO0Z.js +0 -1
  457. package/client/dist/assets/i18n-DwgvwTnx.js +0 -2
  458. package/client/dist/assets/index-DhjjFyU5.js +0 -66
  459. package/client/dist/assets/index-xBFsOEiJ.css +0 -2
  460. package/client/dist/assets/mcp-Czd_iyp0.js +0 -1
  461. package/client/dist/assets/mcp-D2pc5GGx.js +0 -1
  462. package/client/dist/assets/mcp-D4ZjtIx3.js +0 -1
  463. package/client/dist/assets/mcp-DIfBUv2D.js +0 -1
  464. package/client/dist/assets/mcp-DsTwmjST.js +0 -1
  465. package/client/dist/assets/mcp-SMkn8boG.js +0 -1
  466. package/client/dist/assets/mcp-fSfiVYAc.js +0 -1
  467. package/client/dist/assets/mcp-uDkXYu4L.js +0 -1
  468. package/client/dist/assets/narration-4Z2VUhZw.js +0 -1
  469. package/client/dist/assets/narration-BGuiEDj3.js +0 -1
  470. package/client/dist/assets/narration-BuSoqRmv.js +0 -1
  471. package/client/dist/assets/narration-CB9t29fy.js +0 -1
  472. package/client/dist/assets/narration-CU3x8FHD.js +0 -1
  473. package/client/dist/assets/narration-CWeXhfP5.js +0 -1
  474. package/client/dist/assets/narration-DRaDnhT5.js +0 -1
  475. package/client/dist/assets/narration-DtEbQgCw.js +0 -1
  476. package/client/dist/assets/nav-ApJVg3xu.js +0 -1
  477. package/client/dist/assets/nav-BKNky1Bg.js +0 -1
  478. package/client/dist/assets/nav-C6vWWKYx.js +0 -1
  479. package/client/dist/assets/nav-CXJ6Aw89.js +0 -1
  480. package/client/dist/assets/nav-CsmFBIDf.js +0 -1
  481. package/client/dist/assets/nav-DjkmQJjd.js +0 -1
  482. package/client/dist/assets/packet-B1IxaL5f.js +0 -1
  483. package/client/dist/assets/packet-Bh5kkbiB.js +0 -1
  484. package/client/dist/assets/packet-By8fqYxW.js +0 -1
  485. package/client/dist/assets/packet-CZdvA44E.js +0 -1
  486. package/client/dist/assets/packet-CrjHRQCP.js +0 -1
  487. package/client/dist/assets/packet-Dwi2pTtx.js +0 -1
  488. package/client/dist/assets/packet-_-ZIa0IR.js +0 -1
  489. package/client/dist/assets/packet-n0W6E1Hn.js +0 -1
  490. package/client/dist/assets/play-DNlrawbS.js +0 -1
  491. package/client/dist/assets/provider-capabilities-Doej9lfa.js +0 -1
  492. package/client/dist/assets/settings-Bd0x4TBa.js +0 -1
  493. package/client/dist/assets/settings-C1-7cXzC.js +0 -1
  494. package/client/dist/assets/settings-CAFaT38b.js +0 -1
  495. package/client/dist/assets/settings-Ct3v1nW2.js +0 -1
  496. package/client/dist/assets/settings-DHh7_IF5.js +0 -1
  497. package/client/dist/assets/settings-DQXAmdcv.js +0 -1
  498. package/client/dist/assets/settings-guzlhpup.js +0 -1
  499. package/client/dist/assets/settings-sZfTPMEH.js +0 -1
  500. package/client/dist/assets/setup-3jWaoVUO.js +0 -1
  501. package/client/dist/assets/setup-BjgRneb2.js +0 -1
  502. package/client/dist/assets/setup-BzFIjv-f.js +0 -1
  503. package/client/dist/assets/setup-C4VfQsxE.js +0 -1
  504. package/client/dist/assets/setup-DfUkpDbN.js +0 -1
  505. package/client/dist/assets/setup-ECe90Pmu.js +0 -1
  506. package/client/dist/assets/setup-dlqC4Jmm.js +0 -1
  507. package/client/dist/assets/setup-jwfLMiM2.js +0 -1
  508. package/client/dist/assets/spending-BQX3P7Cq.js +0 -1
  509. package/client/dist/assets/useDesktop-BzX769yH.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
  }
@@ -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
+ }
@@ -44,12 +44,16 @@ const framework_manager_1 = require("./framework-manager");
44
44
  const artifact_registry_1 = require("./artifact-registry");
45
45
  const path_resolver_1 = require("./path-resolver");
46
46
  const dev_ports_1 = require("./dev-ports");
47
+ const api_not_found_1 = require("./api-not-found");
47
48
  const process_admission_1 = require("./process-admission");
49
+ const host_control_1 = require("./host-control");
50
+ const transient_children_1 = require("./transient-children");
48
51
  // Side-effect import: registers every bundled ProviderAdapter (claude, codex,
49
52
  // future providers) so `getAdapter`/`hasAdapter`/`listAdapters` are populated
50
53
  // before any manager constructs a project context. See
51
54
  // openspec/changes/add-multi-provider-support/specs/multi-provider-architecture/spec.md.
52
55
  require("./providers");
56
+ const hostControlToken = (0, host_control_1.consumeHostControlToken)();
53
57
  const inheritedPathBeforeResolve = (process.env.PATH ?? '').split(process.platform === 'win32' ? ';' : ':').filter(Boolean).length;
54
58
  // Backfill SystemRoot/ComSpec/etc into process.env BEFORE anything spawns, so a
55
59
  // stripped GUI-launch sidecar env never breaks cmd.exe-mediated spawns (PTY,
@@ -155,6 +159,7 @@ function corsMiddleware(req, res, next) {
155
159
  next();
156
160
  }
157
161
  app.use(corsMiddleware);
162
+ app.use('/api/host', (0, host_control_1.createHostControlRouter)({ token: hostControlToken, onShutdown: shutdown }));
158
163
  // Provider traffic uses the desktop listener as a stable relay. Mount before
159
164
  // every body parser so large/streaming request bodies and Codex event streams
160
165
  // pass through byte-for-byte; the relay verifies the connected Headroom backend
@@ -504,9 +509,17 @@ function applyPtyWsRateLimiting(ws) {
504
509
  // ─── Super-mode bootstrap ─────────────────────────────────────────────────────
505
510
  {
506
511
  const registry = new project_registry_1.ProjectRegistry(broadcast, undefined, port);
512
+ try {
513
+ (0, transient_children_1.initializeBackgroundProcessPersistence)(path_1.default.join(path_1.default.dirname(registry.desktopDb.name), 'background-processes.sqlite'));
514
+ }
515
+ catch (error) {
516
+ // A log-store failure must not take mission chat or the project catalog
517
+ // offline. New background launches report the persistence error directly.
518
+ console.error('[background-process] persistent history unavailable:', error);
519
+ }
507
520
  registry.loadAll();
508
521
  _registry = registry;
509
- _getProjectCount = () => registry.listContexts().length;
522
+ _getProjectCount = () => registry.listProjects().length;
510
523
  // ─── Bundled framework update channel (Phase 6) ─────────────────────────────
511
524
  // After the registry + projects have loaded, run a single framework
512
525
  // version-check. On FIRST run it materializes the bundled framework to
@@ -606,6 +619,15 @@ function applyPtyWsRateLimiting(ws) {
606
619
  broadcast,
607
620
  }));
608
621
  if (mcpManager.isEnabledSetting()) {
622
+ // MCP is on by default, so mint + persist the scoped token at boot: the
623
+ // stdio bridge reads ~/.specrails/mcp.token locally, and on a fresh install
624
+ // nothing else would have written it until Settings ▸ MCP was opened.
625
+ try {
626
+ (0, mcp_1.getMcpToken)();
627
+ }
628
+ catch (err) {
629
+ console.warn('[mcp] could not prepare the scoped token at boot:', err);
630
+ }
609
631
  mcpManager.start().catch((err) => console.error('[mcp] boot start failed:', err));
610
632
  }
611
633
  // ─── App-global agent chat (drives the app via its own MCP) ────────────────
@@ -660,7 +682,7 @@ function applyPtyWsRateLimiting(ws) {
660
682
  state.subscribedProjectId = frame.projectId;
661
683
  });
662
684
  // Send app state init
663
- const projects = registry.listContexts().map((ctx) => ctx.project);
685
+ const projects = registry.listProjects();
664
686
  ws.send(JSON.stringify({
665
687
  type: 'desktop.projects',
666
688
  projects,
@@ -680,6 +702,7 @@ app.use((err, _req, res, _next) => {
680
702
  }
681
703
  });
682
704
  // ─── Serve built React client (production) ────────────────────────────────────
705
+ app.use(['/api', '/hooks'], api_not_found_1.apiNotFound);
683
706
  const clientDist = path_1.default.resolve(__dirname, '../../client/dist');
684
707
  if (fs_1.default.existsSync(clientDist)) {
685
708
  app.use(express_1.default.static(clientDist));
@@ -699,6 +722,13 @@ server.on('error', (err) => {
699
722
  server.listen(port, '127.0.0.1', () => {
700
723
  console.log(`specrails web manager running on http://127.0.0.1:${port}`);
701
724
  writePidFile();
725
+ // Milestone launch chains launch chunks over loopback REST, so a chain whose
726
+ // chunk settled while the server was down can only replay once we listen.
727
+ for (const ctx of _registry?.listContexts() ?? []) {
728
+ void ctx.milestoneChains?.recoverOnStartup().catch((err) => {
729
+ console.error(`[milestone-chain] startup recovery failed for ${ctx.project.id}:`, err);
730
+ });
731
+ }
702
732
  // Sweep stale shell-integration shim directories left behind by previous runs.
703
733
  try {
704
734
  const removed = (0, terminal_shell_integration_1.cleanupStaleShimDirs)();
@@ -744,6 +774,17 @@ async function shutdown() {
744
774
  Promise.resolve().then(() => _agentChatManager?.shutdown()),
745
775
  Promise.resolve().then(() => _blueprintChatManager?.shutdown()),
746
776
  ]);
777
+ // The shell can exit before its application descendants. Keep the event
778
+ // loop alive for the owned-group escalation before server.close exits us.
779
+ const remainingBackground = await (0, transient_children_1.awaitBackgroundProcessesStopped)(6000);
780
+ if (remainingBackground.length)
781
+ console.error('[shutdown] background applications still alive after stop deadline:', remainingBackground.map(({ pid, processId, error }) => ({ pid, processId, error })));
782
+ try {
783
+ (0, transient_children_1.closeBackgroundProcessPersistence)();
784
+ }
785
+ catch (error) {
786
+ console.error('[shutdown] background process history could not be flushed:', error);
787
+ }
747
788
  try {
748
789
  await _headroomManager?.shutdown();
749
790
  }