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
@@ -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
  }
@@ -40,6 +40,9 @@ const result_event_1 = require("./result-event");
40
40
  const db_1 = require("./db");
41
41
  const stream_display_1 = require("./util/stream-display");
42
42
  const runtime_1 = require("./providers/runtime");
43
+ const terminal_result_1 = require("./providers/terminal-result");
44
+ const claude_background_tasks_1 = require("./providers/claude-background-tasks");
45
+ const loop_constants_1 = require("./loop-constants");
43
46
  /** Claude's stream-json result frames do not carry a turn id. Hash a canonical
44
47
  * representation of the real frame instead: an exact/semantic retransmission
45
48
  * remains identifiable even after the next stdin turn has been armed, while a
@@ -58,6 +61,11 @@ function canonicalJson(value) {
58
61
  function resultFrameSignature(frame) {
59
62
  return (0, node_crypto_1.createHash)('sha256').update(canonicalJson(frame)).digest('hex');
60
63
  }
64
+ /** Bound unproductive reply/continue cycles for the same unfinished workers.
65
+ * A blocking collection can take as long as the owner's ordinary step budget;
66
+ * repeatedly returning without joining the workers cannot spin forever. */
67
+ const MAX_BACKGROUND_HANDOFFS = 3;
68
+ const MAX_TOTAL_BACKGROUND_HANDOFFS = 12;
61
69
  /** The claude CLI's synthetic no-op marker: the result text of a frame emitted
62
70
  * for a command the CLI could not resolve (nothing was sent to the model). */
63
71
  exports.UNKNOWN_COMMAND_RE = /^Unknown command:/;
@@ -120,6 +128,7 @@ class InteractiveJobSession {
120
128
  _onSettle;
121
129
  _settleMode;
122
130
  _zombieTimeoutMs;
131
+ _onZombieTimeoutHook;
123
132
  _quiescentEofGraceMs;
124
133
  /** Armed by the quiescent graceful teardown: SIGTERM escalation if the child
125
134
  * ignores the stdin EOF. Cleared on close/settle/dispose. */
@@ -158,11 +167,16 @@ class InteractiveJobSession {
158
167
  _sessionId = null;
159
168
  /** Last completed turn's `result` payload (output chaining — see SettleInfo). */
160
169
  _resultText = null;
170
+ _resultIsError = false;
161
171
  /** True once ANY assistant-derived event was observed across the whole
162
172
  * session life (isModelWorkEvent). Feeds the zero-work settle predicate:
163
173
  * real turns always emit assistant events; the synthetic `Unknown command:`
164
174
  * frame never does. */
165
175
  _sawModelWork = false;
176
+ /** Authoritative live roster, never a reason to complete an automatic step. */
177
+ _liveBackgroundTasks = [];
178
+ _backgroundHandoffs = 0;
179
+ _totalBackgroundHandoffs = 0;
166
180
  /** Previous turn's CUMULATIVE `total_cost_usd` / `num_turns` reading. The
167
181
  * persistent stream-json transport reports both cumulatively per turn, so we
168
182
  * accumulate deltas against these baselines (HIGH-2). Reset to 0 when the
@@ -199,6 +213,7 @@ class InteractiveJobSession {
199
213
  this._onSettle = deps.onSettle;
200
214
  this._settleMode = deps.settleMode ?? 'finalize';
201
215
  this._zombieTimeoutMs = deps.zombieTimeoutMs ?? 0;
216
+ this._onZombieTimeoutHook = deps.onZombieTimeout;
202
217
  this._quiescentEofGraceMs = deps.quiescentEofGraceMs ?? 5000;
203
218
  this._nextEventSeq = deps.nextEventSeq ?? null;
204
219
  this._persistTurnUsage = deps.persistTurnUsage ?? null;
@@ -208,12 +223,37 @@ class InteractiveJobSession {
208
223
  }
209
224
  /** Spawn the resident child and run the first turn (the freestyle prompt). */
210
225
  start(spec, firstPrompt) {
211
- const child = this._spawn(spec.binary, spec.args, {
212
- env: spec.env ?? process.env,
213
- // stdin MUST be piped — it is the per-turn transport.
214
- stdio: ['pipe', 'pipe', 'pipe'],
215
- cwd: spec.cwd,
216
- });
226
+ // Keep slash-command expansion intact: the lifecycle rule belongs in the
227
+ // system context, not in the command's argument string. Preserve any
228
+ // existing appended system prompt supplied by the queue/loop owner.
229
+ const args = [...spec.args];
230
+ if (this._adapter.id === 'claude' && this._settleMode === 'auto') {
231
+ const index = args.indexOf('--append-system-prompt');
232
+ if (index >= 0)
233
+ args[index + 1] = `${args[index + 1] ?? ''}\n\n${loop_constants_1.FOREGROUND_RULE}`;
234
+ else
235
+ args.push('--append-system-prompt', loop_constants_1.FOREGROUND_RULE);
236
+ }
237
+ let child;
238
+ try {
239
+ child = this._spawn(spec.binary, args, {
240
+ env: spec.env ?? process.env,
241
+ // stdin MUST be piped — it is the per-turn transport.
242
+ stdio: ['pipe', 'pipe', 'pipe'],
243
+ cwd: spec.cwd,
244
+ });
245
+ }
246
+ catch (error) {
247
+ // Local preparation (including managed skills) can fail synchronously,
248
+ // before there is a child to emit error/close. Settle through the normal
249
+ // owner callback so no interactive slot or step timer leaks.
250
+ this._resultText = `Could not start ${this._adapter.id}: ${error instanceof Error ? error.message : String(error)}`;
251
+ this._resultIsError = true;
252
+ this._persistLog('stderr', this._resultText);
253
+ this._emitLog('stderr', this._resultText);
254
+ this._settle('crashed');
255
+ return;
256
+ }
217
257
  this._child = child;
218
258
  this._childClosed = false;
219
259
  this._stdinFailed = false;
@@ -272,7 +312,7 @@ class InteractiveJobSession {
272
312
  * an immediate prompt only after a confirmed stdin write. Returns false if the
273
313
  * session is gone OR the write could not be delivered (stdin destroyed/EPIPE). */
274
314
  send(text) {
275
- if (this._disposed || this._finalizing || !this._child)
315
+ if (this._disposed || this._settled || this._finalizing || !this._child)
276
316
  return false;
277
317
  const queued = this._streaming;
278
318
  if (queued) {
@@ -392,6 +432,8 @@ class InteractiveJobSession {
392
432
  * write; false when stdin is gone/destroyed or the write throws (EPIPE) — the
393
433
  * caller must NOT treat an unconfirmed turn as accepted. */
394
434
  _writeTurn(text) {
435
+ if (this._disposed || this._settled || this._finalizing || this._terminationStarted)
436
+ return false;
395
437
  const child = this._child;
396
438
  if (!child || !child.stdin || child.stdin.destroyed || this._stdinFailed)
397
439
  return false;
@@ -431,20 +473,39 @@ class InteractiveJobSession {
431
473
  parsed = JSON.parse(line);
432
474
  }
433
475
  catch { /* plain text */ }
476
+ // Keep the live roster current before deciding whether a result completes
477
+ // the step or needs the parent to join its unfinished workers.
478
+ this._trackBackgroundTasks(parsed);
479
+ // parseStreamEvents is pure, so classifying the frame through the adapter
480
+ // BEFORE the drop logic is safe — and it is the adapter that knows a claude
481
+ // `result` frame carrying `origin.kind === 'task-notification'` is a
482
+ // CLI-internal notification turn, not this turn's terminal result.
483
+ const adapterEvents = (0, runtime_1.parseStreamEvents)(this._adapter, line);
434
484
  // A late retransmission can arrive after the next queued prompt has already
435
- // been written. Drop it BEFORE adapter parsing and event persistence: adding
436
- // it to the next turn's event buffer corrupts live totals, while persisting
437
- // it beyond the prior checkpoint would make crash recovery count it again.
485
+ // been written. Drop it BEFORE event persistence: adding it to the next
486
+ // turn's event buffer corrupts live totals, while persisting it beyond the
487
+ // prior checkpoint would make crash recovery count it again.
438
488
  if (parsed?.type === 'result') {
439
489
  // A terminal frame with no active turn is causally unassignable. Drop it
440
- // before adapter parsing/persistence so raw recovery cannot count it.
490
+ // before persistence so raw recovery cannot count it.
441
491
  if (!this._awaitingResult)
442
492
  return;
493
+ if (!adapterEvents.some((ev) => ev.kind === 'result')) {
494
+ // Notification turn (loop run 5c958db2): the CLI answered its OWN
495
+ // orphaned-background-task notice, not the prompt we wrote — the real
496
+ // turn is still coming. Closing the turn here auto-settled the session
497
+ // and tore the child down mid-thought as a zero-work failure. Leave the
498
+ // turn armed, and do not persist the frame as a `result` row either
499
+ // (crash recovery counts persisted result rows as completed turns).
500
+ const note = `↷ ${this._adapter.id} reported a background-task notification (not this turn's result) — continuing.`;
501
+ this._persistLog('stdout', note);
502
+ this._emitLog('stdout', note);
503
+ return;
504
+ }
443
505
  const signature = resultFrameSignature(parsed);
444
506
  if (this._acceptedResultSignatures.has(signature))
445
507
  return;
446
508
  }
447
- const adapterEvents = (0, runtime_1.parseStreamEvents)(this._adapter, line);
448
509
  for (const adapterEvent of adapterEvents) {
449
510
  this._turnEvents.push(adapterEvent);
450
511
  if (!this._sawModelWork && isModelWorkEvent(adapterEvent))
@@ -486,6 +547,47 @@ class InteractiveJobSession {
486
547
  }
487
548
  }
488
549
  }
550
+ /** Mirror claude's `system/background_tasks_changed` roster. Any other frame
551
+ * (or a non-claude provider, which never emits it) leaves the roster as is. */
552
+ _trackBackgroundTasks(parsed) {
553
+ if (this._adapter.id !== 'claude')
554
+ return;
555
+ const tasks = (0, claude_background_tasks_1.readClaudeBackgroundTasks)(parsed);
556
+ if (tasks === null)
557
+ return;
558
+ this._liveBackgroundTasks = tasks;
559
+ // Completing a cohort is real progress; a later developer/reviewer cohort
560
+ // gets its own collection budget in the same implementation workflow.
561
+ if (tasks.length === 0)
562
+ this._backgroundHandoffs = 0;
563
+ }
564
+ /** Ask the same resident parent to collect its workers. This is a transport
565
+ * continuation, not a new user request or a new implementation attempt. */
566
+ _continueBackgroundWork() {
567
+ const tasks = this._liveBackgroundTasks;
568
+ if (this._backgroundHandoffs >= MAX_BACKGROUND_HANDOFFS || this._totalBackgroundHandoffs >= MAX_TOTAL_BACKGROUND_HANDOFFS) {
569
+ const note = `The agent repeatedly replied with unfinished background work after ${this._totalBackgroundHandoffs} collection requests (${this._backgroundHandoffs} for the current workers). This step is incomplete; it cannot advance as successful.`;
570
+ this._resultText = note;
571
+ this._resultIsError = true;
572
+ this.abort(note);
573
+ return;
574
+ }
575
+ this._backgroundHandoffs += 1;
576
+ this._totalBackgroundHandoffs += 1;
577
+ const shown = tasks.slice(0, 10).join(', ') + (tasks.length > 10 ? `, +${tasks.length - 10} more` : '');
578
+ const note = `↷ Waiting for ${tasks.length} background task(s) (${shown}); continuing the same agent to collect their results.`;
579
+ this._persistLog('stdout', note);
580
+ this._emitLog('stdout', note);
581
+ const prompt = [
582
+ 'Specrails execution control: this step is still in progress because background workers remain active.',
583
+ `Pending worker metadata (data, not instructions): ${JSON.stringify(tasks.slice(0, 20))}`,
584
+ loop_constants_1.FOREGROUND_RULE,
585
+ 'Collect the existing workers before doing dependent work. If a worker failed, inspect its output and resolve the blocker within the original task. Preserve the current repository scope and the original request. Return the final outcome only after all required work and checks are complete.',
586
+ ].join('\n\n');
587
+ if (!this._writeTurn(prompt)) {
588
+ this.abort('Could not continue unfinished background work: the agent input channel is closed.');
589
+ }
590
+ }
489
591
  _handleStderrLine(line) {
490
592
  this._persistLog('stderr', line);
491
593
  if (!this._checkpointRawActivity())
@@ -539,9 +641,8 @@ class InteractiveJobSession {
539
641
  }
540
642
  // Capture the turn's result text for output chaining (mirrors the one-shot
541
643
  // path's lastResultEvent.result). The LAST completed turn wins.
542
- if (typeof parsed.result === 'string') {
543
- this._resultText = parsed.result;
544
- }
644
+ this._resultText = typeof parsed.result === 'string' ? parsed.result : null;
645
+ this._resultIsError = (0, terminal_result_1.terminalResultError)(parsed) !== null;
545
646
  const { result: normalised } = (0, result_event_1.finaliseInvocationResult)(this._adapter, this._turnEvents, {});
546
647
  // COST-ACCOUNTING-AUDIT HIGH-2: this ONE resident `claude -p --input-format
547
648
  // stream-json` child reports `total_cost_usd` and `num_turns` CUMULATIVELY
@@ -614,6 +715,21 @@ class InteractiveJobSession {
614
715
  totals: { ...this._accum },
615
716
  timestamp: new Date().toISOString(),
616
717
  });
718
+ const resultError = (0, terminal_result_1.terminalResultError)(parsed);
719
+ if (resultError) {
720
+ // The terminal result still owns real usage: checkpoint it above before
721
+ // ending the session. Never auto-finalize or dispatch a queued prompt
722
+ // after a max-turns/provider execution failure, even if stdout previously
723
+ // included a PASS or the resident child would eventually exit with 0.
724
+ this._finalizing = true;
725
+ this._terminationReason = 'crashed';
726
+ this._clearZombieTimer();
727
+ const note = `✖ Provider turn failed — ${resultError}`;
728
+ this._persistLog('stderr', note);
729
+ this._emitLog('stderr', note);
730
+ this._terminateChildTree('crashed');
731
+ return;
732
+ }
617
733
  // Feed the next queued prompt (if any) now that the turn is idle. Deferred to a
618
734
  // microtask so any stray/duplicate result for THIS just-settled turn that is
619
735
  // sitting in the same synchronous stdout batch is processed (and rejected by
@@ -645,7 +761,10 @@ class InteractiveJobSession {
645
761
  return;
646
762
  if (this._streaming || this._pending.length > 0)
647
763
  return;
648
- this._finalizeQuiescent();
764
+ if (this._liveBackgroundTasks.length > 0)
765
+ this._continueBackgroundWork();
766
+ else
767
+ this._finalizeQuiescent();
649
768
  });
650
769
  }
651
770
  }
@@ -815,11 +934,19 @@ class InteractiveJobSession {
815
934
  const timeoutSec = Math.round(this._zombieTimeoutMs / 1000);
816
935
  const note = `[zombie-detection] Interactive job ${this._jobId} produced no output for ${timeoutSec}s — auto-terminating`;
817
936
  console.error(note);
937
+ try {
938
+ this._onZombieTimeoutHook?.();
939
+ }
940
+ catch { /* owner hook must never block teardown */ }
818
941
  this.abort(note);
819
942
  }
820
943
  _settle(reason) {
821
944
  if (this._settled)
822
945
  return;
946
+ // A terminal error received during graceful teardown overrides an earlier
947
+ // finalize request, including a teardown timer armed before the error.
948
+ if (this._terminationReason === 'crashed')
949
+ reason = 'crashed';
823
950
  this._settled = true;
824
951
  // Fold any unfinished turn BEFORE snapshotting totals so a mid-turn
825
952
  // finalize/crash keeps that turn's spend (CRIT-4).
@@ -867,6 +994,7 @@ class InteractiveJobSession {
867
994
  estimated: this._inflightEstimated,
868
995
  activeDurationMs: this._activeDurationMs,
869
996
  resultText: this._resultText,
997
+ resultIsError: this._resultIsError,
870
998
  zeroWork,
871
999
  });
872
1000
  }
@@ -882,6 +1010,11 @@ class InteractiveJobSession {
882
1010
  * settles because its owner already flushed/reconciles the job separately.
883
1011
  */
884
1012
  _terminateChildTree(reason) {
1013
+ // Close admission synchronously, before SIGTERM. A child may take seconds
1014
+ // to exit; neither a queued user prompt nor an auto-collection microtask
1015
+ // may start another turn in that window (including a late result frame).
1016
+ this._finalizing = true;
1017
+ this._clearZombieTimer();
885
1018
  if (reason !== null && this._terminationReason === null)
886
1019
  this._terminationReason = reason;
887
1020
  const child = this._child;
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ // Loopback client for the app's OWN REST API, authenticated with the master
3
+ // token held server-side. Precedent: the MCP domain tools (`apiCall`), which
4
+ // reuse every router's validation instead of re-implementing it. The milestone
5
+ // launch chain uses it to launch chunks through the ordinary rails launch
6
+ // route — the single authority for its ~15 guards — so a chunk launch behaves
7
+ // byte-identically to a dashboard launch and every 4xx becomes a typed reason.
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.createInternalApi = createInternalApi;
10
+ exports.internalApiError = internalApiError;
11
+ const auth_1 = require("./auth");
12
+ function createInternalApi(deps) {
13
+ const fetchImpl = deps.fetchImpl ?? fetch;
14
+ const token = deps.token ?? auth_1.loadOrGenerateToken;
15
+ return {
16
+ async call(method, path, body) {
17
+ const res = await fetchImpl(`http://127.0.0.1:${deps.port}/api${path}`, {
18
+ method,
19
+ headers: {
20
+ Authorization: `Bearer ${token()}`,
21
+ ...(body !== undefined ? { 'content-type': 'application/json' } : {}),
22
+ },
23
+ body: body !== undefined ? JSON.stringify(body) : undefined,
24
+ });
25
+ const text = await res.text();
26
+ let data;
27
+ try {
28
+ data = text ? JSON.parse(text) : null;
29
+ }
30
+ catch {
31
+ data = text;
32
+ }
33
+ return { ok: res.ok, status: res.status, body: data };
34
+ },
35
+ };
36
+ }
37
+ /** Extract the router's `{ error, detail }` from a failed response body. */
38
+ function internalApiError(res) {
39
+ const body = res.body;
40
+ if (body && typeof body === 'object') {
41
+ const obj = body;
42
+ return {
43
+ error: typeof obj.error === 'string' ? obj.error : `http_${res.status}`,
44
+ ...(typeof obj.detail === 'string' ? { detail: obj.detail } : {}),
45
+ };
46
+ }
47
+ return { error: `http_${res.status}`, ...(typeof body === 'string' && body ? { detail: body.slice(0, 200) } : {}) };
48
+ }
@@ -131,6 +131,7 @@ function mapIssueToTicket(issue, localId, conn, existing) {
131
131
  labels: issue.fields.labels ?? [],
132
132
  assignee: issue.fields.assignee?.displayName ?? issue.fields.assignee?.emailAddress ?? null,
133
133
  prerequisites: existing?.prerequisites ?? [],
134
+ ...(existing?.repositoryIds ? { repositoryIds: [...existing.repositoryIds] } : {}),
134
135
  metadata: existing?.metadata ?? {},
135
136
  comments: existing?.comments ?? [],
136
137
  attachments: existing?.attachments,
@@ -0,0 +1,174 @@
1
+ "use strict";
2
+ // ─── Tolerant JSON parsing for model-emitted payloads ─────────────────────────
3
+ //
4
+ // A model writing a ~20 KB JSON snapshot by hand makes small, predictable
5
+ // mistakes: a raw newline inside a string, an unescaped inner quote, a
6
+ // trailing comma, a nested ```json fence, a stray comment. `JSON.parse`
7
+ // rejects all of them and the whole snapshot used to be dropped silently.
8
+ //
9
+ // `parseJsonTolerant` first tries strict `JSON.parse`; only when that fails
10
+ // does it run ONE deterministic repair pass (a small string-aware state
11
+ // machine — never a regex over the whole text, so repairs inside string
12
+ // literals can't corrupt structure) and parses again. The result says whether
13
+ // a repair was needed and which kinds were applied, so callers can log/badge
14
+ // it. It never throws.
15
+ //
16
+ // Client mirror: client/src/lib/json-tolerant.ts — keep the two in sync.
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.repairJsonText = repairJsonText;
19
+ exports.parseJsonTolerant = parseJsonTolerant;
20
+ const VALID_ESCAPES = '"\\/bfnrtu';
21
+ function isJsonWhitespace(ch) {
22
+ return ch === ' ' || ch === '\t' || ch === '\r' || ch === '\n';
23
+ }
24
+ /** One string-aware pass over `source` fixing the common model mistakes. */
25
+ function repairJsonText(source) {
26
+ const repairs = new Set();
27
+ let text = source.trim();
28
+ // A nested code fence around the payload (```json … ``` inside the
29
+ // blueprint-draft block). Leading and trailing fences are stripped
30
+ // independently: the outer lazy fence regex may already have eaten one.
31
+ const leadingFence = /^```[A-Za-z0-9_-]*[ \t]*\r?\n/.exec(text);
32
+ if (leadingFence) {
33
+ text = text.slice(leadingFence[0].length);
34
+ repairs.add('nested_fence');
35
+ }
36
+ const trailingFence = /\r?\n[ \t]*```[ \t]*$/.exec(text);
37
+ if (trailingFence) {
38
+ text = text.slice(0, trailingFence.index);
39
+ repairs.add('nested_fence');
40
+ }
41
+ // Prose before/after the object ("Here is the snapshot: {...} Done.").
42
+ const first = text.indexOf('{');
43
+ const last = text.lastIndexOf('}');
44
+ if (first !== -1 && last > first && (first > 0 || last < text.length - 1)) {
45
+ text = text.slice(first, last + 1);
46
+ repairs.add('trimmed_wrapper');
47
+ }
48
+ let out = '';
49
+ let inString = false;
50
+ for (let i = 0; i < text.length; i++) {
51
+ const ch = text[i];
52
+ if (inString) {
53
+ if (ch === '\\') {
54
+ const next = text[i + 1];
55
+ if (next === undefined) {
56
+ repairs.add('dangling_escape');
57
+ break;
58
+ }
59
+ if (VALID_ESCAPES.includes(next)) {
60
+ out += ch + next;
61
+ i += 1;
62
+ continue;
63
+ }
64
+ // `\'` and friends are not JSON escapes — keep the character, drop the
65
+ // backslash (a raw newline after a backslash becomes a real escape).
66
+ out += next === '\n' ? '\\n' : next;
67
+ i += 1;
68
+ repairs.add('invalid_escape');
69
+ continue;
70
+ }
71
+ if (ch === '"') {
72
+ // Terminator, or a stray inner quote? A real terminator is followed
73
+ // (after whitespace) by a structural character or the end of input.
74
+ let j = i + 1;
75
+ while (j < text.length && isJsonWhitespace(text[j]))
76
+ j += 1;
77
+ const after = text[j];
78
+ if (after === undefined || after === ',' || after === '}' || after === ']' || after === ':') {
79
+ inString = false;
80
+ out += ch;
81
+ continue;
82
+ }
83
+ out += '\\"';
84
+ repairs.add('inner_quote');
85
+ continue;
86
+ }
87
+ if (ch === '\n') {
88
+ out += '\\n';
89
+ repairs.add('raw_newline');
90
+ continue;
91
+ }
92
+ if (ch === '\r') {
93
+ repairs.add('raw_newline');
94
+ continue;
95
+ }
96
+ if (ch === '\t') {
97
+ out += '\\t';
98
+ repairs.add('raw_control');
99
+ continue;
100
+ }
101
+ const code = ch.charCodeAt(0);
102
+ if (code < 0x20) {
103
+ out += '\\u' + code.toString(16).padStart(4, '0');
104
+ repairs.add('raw_control');
105
+ continue;
106
+ }
107
+ out += ch;
108
+ continue;
109
+ }
110
+ // Outside a string literal.
111
+ if (ch === '"') {
112
+ inString = true;
113
+ out += ch;
114
+ continue;
115
+ }
116
+ if (ch === ',') {
117
+ let j = i + 1;
118
+ while (j < text.length && isJsonWhitespace(text[j]))
119
+ j += 1;
120
+ if (text[j] === '}' || text[j] === ']') {
121
+ repairs.add('trailing_comma');
122
+ continue;
123
+ }
124
+ out += ch;
125
+ continue;
126
+ }
127
+ if (ch === '/' && text[i + 1] === '/') {
128
+ const end = text.indexOf('\n', i);
129
+ i = end === -1 ? text.length : end;
130
+ repairs.add('comment');
131
+ continue;
132
+ }
133
+ if (ch === '/' && text[i + 1] === '*') {
134
+ const end = text.indexOf('*/', i + 2);
135
+ i = end === -1 ? text.length : end + 1;
136
+ repairs.add('comment');
137
+ continue;
138
+ }
139
+ out += ch;
140
+ }
141
+ return { text: out, repairs: [...repairs] };
142
+ }
143
+ function describeParseError(err, text) {
144
+ const message = err instanceof Error ? err.message : String(err);
145
+ const match = /position (\d+)/i.exec(message);
146
+ const position = match ? Number(match[1]) : null;
147
+ let excerpt = null;
148
+ if (position !== null && Number.isFinite(position)) {
149
+ const start = Math.max(0, position - 40);
150
+ const end = Math.min(text.length, position + 40);
151
+ excerpt = text.slice(start, end).replace(/\s+/g, ' ');
152
+ }
153
+ return { error: message, position, excerpt };
154
+ }
155
+ /**
156
+ * Strict `JSON.parse` first; on failure ONE repair pass, then parse again.
157
+ * `repaired` is true only when the strict parse failed AND the repaired text
158
+ * parsed. Never throws.
159
+ */
160
+ function parseJsonTolerant(source) {
161
+ try {
162
+ return { ok: true, value: JSON.parse(source), repaired: false, repairs: [] };
163
+ }
164
+ catch (strictErr) {
165
+ const { text, repairs } = repairJsonText(source);
166
+ try {
167
+ const value = JSON.parse(text);
168
+ return { ok: true, value, repaired: true, repairs };
169
+ }
170
+ catch {
171
+ return { ok: false, ...describeParseError(strictErr, source) };
172
+ }
173
+ }
174
+ }
@@ -227,8 +227,10 @@ function moveOverwriting(src, dest) {
227
227
  fs.renameSync(src, dest);
228
228
  }
229
229
  catch {
230
- // Cross-device fallback.
231
- fs.cpSync(src, dest, { recursive: true });
230
+ // Cross-device fallback. JS traversal avoids Node 22's native Unicode copy
231
+ // failure on Windows (nodejs/node#61878). The destination is cleared before
232
+ // this copy; remove the source state only after copying succeeds.
233
+ fs.cpSync(src, dest, { recursive: true, filter: () => true, mode: fs.constants.COPYFILE_FICLONE });
232
234
  fs.rmSync(src, { recursive: true, force: true });
233
235
  }
234
236
  }