specrails-desktop 2.26.2 → 2.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (319) hide show
  1. package/client/dist/assets/{ActivityFeedPage-bDe4VitF.js → ActivityFeedPage-DcMmDAP3.js} +1 -1
  2. package/client/dist/assets/AgentBrowserCapture-CZ3oc1UL.js +1 -0
  3. package/client/dist/assets/AgentIntegrationsModal-DkqooCFn.js +1 -0
  4. package/client/dist/assets/{AgentModeCodePane-Ci39DcOo.js → AgentModeCodePane-CM77ljte.js} +2 -2
  5. package/client/dist/assets/AgentModeJobsPane-DbZzogbi.js +1 -0
  6. package/client/dist/assets/AgentsPage-BZIml7eL.js +86 -0
  7. package/client/dist/assets/{AnalyticsPage-Da21wWWw.js → AnalyticsPage-B4qflNxi.js} +1 -1
  8. package/client/dist/assets/{BarChart-BiLTq3uC.js → BarChart-Ckstq9fl.js} +1 -1
  9. package/client/dist/assets/CodePage-DFZXLbI1.js +2 -0
  10. package/client/dist/assets/{DesktopAnalyticsPage-COkzgMmd.js → DesktopAnalyticsPage-DbgMa8OO.js} +1 -1
  11. package/client/dist/assets/DocsDialog-BQc9Kwev.js +11 -0
  12. package/client/dist/assets/DocsPage-OsV44ECy.js +11 -0
  13. package/client/dist/assets/{ExportDropdown-DDhJH-Jr.js → ExportDropdown-a43XHO3Y.js} +1 -1
  14. package/client/dist/assets/IntegrationsPage-C3E8hZhB.js +1 -0
  15. package/client/dist/assets/InteractiveJobComposer-zj8SrOAl.js +19 -0
  16. package/client/dist/assets/JobDetailModal-Cm6ZeVoh.js +1 -0
  17. package/client/dist/assets/JobDetailPage-DN67LsdX.js +1 -0
  18. package/client/dist/assets/JobsPage-DF9wKGbw.js +1 -0
  19. package/client/dist/assets/{LoopBuilderPage-DsLLrtHa.js → LoopBuilderPage-RJ6GGjli.js} +2 -2
  20. package/client/dist/assets/{LoopPreviewModal-C-fDEgnu.js → LoopPreviewModal-B2wmifF6.js} +1 -1
  21. package/client/dist/assets/LoopsPage-DdlvH-dJ.js +1 -0
  22. package/client/dist/assets/PluginsPage-9DI0a6jI.js +2 -0
  23. package/client/dist/assets/ProjectSettingsDialog-dgy5hqPd.js +1 -0
  24. package/client/dist/assets/{TemplatePreviewModal-DcHFbLA8.js → TemplatePreviewModal-CERf1IYB.js} +1 -1
  25. package/client/dist/assets/agent-BRJCESkc2.js +1 -0
  26. package/client/dist/assets/agent-BuYz6fJd.js +1 -0
  27. package/client/dist/assets/agent-CxnRzL6Z.js +1 -0
  28. package/client/dist/assets/agent-D4JAuEGN2.js +1 -0
  29. package/client/dist/assets/agent-DNQlI8EN.js +1 -0
  30. package/client/dist/assets/agent-DyzUH89R.js +1 -0
  31. package/client/dist/assets/agent-MaTxBlg5.js +1 -0
  32. package/client/dist/assets/agent-ostmMYNz2.js +1 -0
  33. package/client/dist/assets/{brain-BBNUUrUI.js → brain-BPbhM1Jx.js} +1 -1
  34. package/client/dist/assets/dashboard-5C2FOZQX.js +1 -0
  35. package/client/dist/assets/{dashboard-C6w0rICe.js → dashboard-B6iQ-IL_.js} +1 -1
  36. package/client/dist/assets/dashboard-BFfnCtq52.js +1 -0
  37. package/client/dist/assets/dashboard-BIC8KtRf2.js +1 -0
  38. package/client/dist/assets/dashboard-CEgg9NAr.js +1 -0
  39. package/client/dist/assets/dashboard-CiOyDh9L.js +1 -0
  40. package/client/dist/assets/dashboard-D6x5OnNj2.js +1 -0
  41. package/client/dist/assets/dashboard-DBuLfWo9.js +1 -0
  42. package/client/dist/assets/{dist-js-BxjTIvdT.js → dist-js-Byjcju1W.js} +1 -1
  43. package/client/dist/assets/{dist-js-DdP_Gl3p.js → dist-js-Dh-9lbLD.js} +1 -1
  44. package/client/dist/assets/index-89SuhD4Y.js +158 -0
  45. package/client/dist/assets/index-DOGzWJhO.css +2 -0
  46. package/client/dist/assets/integrations-By80EBkf.js +1 -0
  47. package/client/dist/assets/integrations-CAjq5tdW2.js +1 -0
  48. package/client/dist/assets/integrations-CG2dp3Kd.js +1 -0
  49. package/client/dist/assets/integrations-CP_aiV_8.js +1 -0
  50. package/client/dist/assets/integrations-CUNe3t9l2.js +1 -0
  51. package/client/dist/assets/integrations-CWjh5VRk.js +1 -0
  52. package/client/dist/assets/integrations-JOHSCvB3.js +1 -0
  53. package/client/dist/assets/integrations-RJfzXkPx2.js +1 -0
  54. package/client/dist/assets/{lib-BviHZ_gK.js → lib-yKyTNJ1c.js} +1 -1
  55. package/client/dist/assets/nav-B2_ToNqS.js +1 -0
  56. package/client/dist/assets/nav-BAMkVEId.js +1 -0
  57. package/client/dist/assets/nav-BTg8s-cD.js +1 -0
  58. package/client/dist/assets/nav-BY_4EWRv.js +1 -0
  59. package/client/dist/assets/nav-CVXtv0wX2.js +1 -0
  60. package/client/dist/assets/nav-D0Erv9ZJ2.js +1 -0
  61. package/client/dist/assets/nav-DUuw9VIU2.js +1 -0
  62. package/client/dist/assets/nav-S4HM94vW.js +1 -0
  63. package/client/dist/assets/settings-BYCMtxOz.js +1 -0
  64. package/client/dist/assets/{settings-BHdPchJP.js → settings-B_v3zgJf.js} +1 -1
  65. package/client/dist/assets/{settings-BeCczBFS2.js → settings-BemR_e3F.js} +1 -1
  66. package/client/dist/assets/{settings-D54_cgl12.js → settings-Bp24JHlv2.js} +1 -1
  67. package/client/dist/assets/{settings-C_nY14Yz.js → settings-BvIwQ3A1.js} +1 -1
  68. package/client/dist/assets/{settings-BlLi719f.js → settings-Bx2ObkTR2.js} +1 -1
  69. package/client/dist/assets/{settings-CaZ26a0S.js → settings-C_T0-j9d.js} +1 -1
  70. package/client/dist/assets/{settings-cn0tbve62.js → settings-DhaFKpcw2.js} +1 -1
  71. package/client/dist/assets/{upload-CRuuzzvp.js → upload-Qh1NE3xi.js} +1 -1
  72. package/client/dist/assets/{value-CIr5hDMt.js → value-BMPeyInx.js} +1 -1
  73. package/client/dist/index.html +29 -29
  74. package/package.json +1 -1
  75. package/server/dist/agent-chat-router.js +2 -0
  76. package/server/dist/agent-operator-prompt.js +17 -1
  77. package/server/dist/db.js +51 -2
  78. package/server/dist/dev-ports.js +35 -0
  79. package/server/dist/global-plugins-router.js +62 -0
  80. package/server/dist/headroom-manager.js +971 -0
  81. package/server/dist/headroom-routing.js +53 -0
  82. package/server/dist/index.js +9 -6
  83. package/server/dist/mcp/guide.js +3 -0
  84. package/server/dist/mcp/tools/catalog.js +2 -0
  85. package/server/dist/mcp/tools/env.js +72 -0
  86. package/server/dist/mcp/tools/jobs.js +110 -2
  87. package/server/dist/path-resolver.js +84 -18
  88. package/server/dist/project-env.js +30 -0
  89. package/server/dist/project-registry.js +4 -2
  90. package/server/dist/project-router-jobs.js +126 -0
  91. package/server/dist/project-router-settings.js +10 -1
  92. package/server/dist/queue-manager.js +9 -1
  93. package/server/dist/rail-isolated-launch.js +5 -4
  94. package/server/dist/rail-pr-decision.js +27 -17
  95. package/server/dist/rail-pr-store.js +36 -0
  96. package/server/dist/rails-router.js +7 -1
  97. package/server/dist/setup-manager.js +32 -29
  98. package/server/dist/setup-prerequisites.js +3 -1
  99. package/server/dist/transient-children.js +266 -9
  100. package/server/dist/util/cli-prompt.js +3 -0
  101. package/server/dist/workspace-resolution.js +3 -3
  102. package/server/dist/worktree-env-discovery.js +172 -0
  103. package/client/dist/assets/AgentBrowserCapture-DQyeEm2V.js +0 -1
  104. package/client/dist/assets/AgentModeJobsPane-C-g1WNVG.js +0 -1
  105. package/client/dist/assets/AgentsPage-BWgglK7V.js +0 -86
  106. package/client/dist/assets/CodePage-BUcAp7SC.js +0 -2
  107. package/client/dist/assets/DocsDialog-PZHMN18B.js +0 -11
  108. package/client/dist/assets/DocsPage-D3fChfkj.js +0 -11
  109. package/client/dist/assets/IntegrationsPage-F87uxxVH.js +0 -3
  110. package/client/dist/assets/InteractiveJobComposer-DqDCiCa0.js +0 -19
  111. package/client/dist/assets/JobDetailModal-BWJR60fi.js +0 -1
  112. package/client/dist/assets/JobDetailPage-CjWE_jyd.js +0 -1
  113. package/client/dist/assets/JobsPage-LmnXkyCa.js +0 -1
  114. package/client/dist/assets/LoopsPage-DDNO9h9T.js +0 -1
  115. package/client/dist/assets/ProjectSettingsDialog-CX854C8e.js +0 -1
  116. package/client/dist/assets/agent-BI6vr-To.js +0 -1
  117. package/client/dist/assets/agent-BUGXI3Ok2.js +0 -1
  118. package/client/dist/assets/agent-Bph_-xt7.js +0 -1
  119. package/client/dist/assets/agent-Bvy2Csyd.js +0 -1
  120. package/client/dist/assets/agent-Bwkyg2oX2.js +0 -1
  121. package/client/dist/assets/agent-CLCyDaZh.js +0 -1
  122. package/client/dist/assets/agent-Cqs14QMr2.js +0 -1
  123. package/client/dist/assets/agent-DJ0EBUuP.js +0 -1
  124. package/client/dist/assets/dashboard-BjjS1OrL.js +0 -1
  125. package/client/dist/assets/dashboard-Bk6oSk1H2.js +0 -1
  126. package/client/dist/assets/dashboard-BrH-GdOg2.js +0 -1
  127. package/client/dist/assets/dashboard-BurhUI8_.js +0 -1
  128. package/client/dist/assets/dashboard-CAUnfDQL2.js +0 -1
  129. package/client/dist/assets/dashboard-C_ljpeYO.js +0 -1
  130. package/client/dist/assets/dashboard-D6OqDdj6.js +0 -1
  131. package/client/dist/assets/index-C695aPdV.css +0 -2
  132. package/client/dist/assets/index-Dw1N2-ci.js +0 -156
  133. package/client/dist/assets/integrations-4KZnYECp.js +0 -1
  134. package/client/dist/assets/integrations-7W266oqF2.js +0 -1
  135. package/client/dist/assets/integrations-BNuauK3o.js +0 -1
  136. package/client/dist/assets/integrations-CHy8tnpV.js +0 -1
  137. package/client/dist/assets/integrations-Cl_wD5AI.js +0 -1
  138. package/client/dist/assets/integrations-CuWnMR5L.js +0 -1
  139. package/client/dist/assets/integrations-Cy8nZ_dv2.js +0 -1
  140. package/client/dist/assets/integrations-zMqY4VU12.js +0 -1
  141. package/client/dist/assets/nav-40HzpIFb.js +0 -1
  142. package/client/dist/assets/nav-8yPhBn00.js +0 -1
  143. package/client/dist/assets/nav-B6PkGfTk2.js +0 -1
  144. package/client/dist/assets/nav-BHqMLrS72.js +0 -1
  145. package/client/dist/assets/nav-CGy4yx3C.js +0 -1
  146. package/client/dist/assets/nav-CXEDcRdv2.js +0 -1
  147. package/client/dist/assets/nav-CzKzuwH8.js +0 -1
  148. package/client/dist/assets/nav-EI_OhEAo.js +0 -1
  149. package/client/dist/assets/settings-D9bDDe2O.js +0 -1
  150. /package/client/dist/assets/{activity-BZNfOdiS.js → activity-BZZlEG3a.js} +0 -0
  151. /package/client/dist/assets/{activity--iN4Ad2R2.js → activity-BbWiDfn-2.js} +0 -0
  152. /package/client/dist/assets/{activity-CIOLxGvh.js → activity-D3Ic-HTS.js} +0 -0
  153. /package/client/dist/assets/{activity-DtxrAUMo2.js → activity-DFbvNqhE2.js} +0 -0
  154. /package/client/dist/assets/{activity-D8vQJrpW.js → activity-Dbl71S-E.js} +0 -0
  155. /package/client/dist/assets/{activity-BuVo5sPB.js → activity-DtzRy92x.js} +0 -0
  156. /package/client/dist/assets/{activity-6hM_ZSg9.js → activity-DvECTYGt.js} +0 -0
  157. /package/client/dist/assets/{activity-D2r2h3SE2.js → activity-FleOlK0l2.js} +0 -0
  158. /package/client/dist/assets/{addspec-D-JLDsUV.js → addspec-BKrH2Ucz.js} +0 -0
  159. /package/client/dist/assets/{addspec-DfhDbg5c.js → addspec-B_LQMHW3.js} +0 -0
  160. /package/client/dist/assets/{addspec-bsNDdqRm2.js → addspec-Bw_YQwo62.js} +0 -0
  161. /package/client/dist/assets/{addspec-BMtLZLW62.js → addspec-CFxGvmJR2.js} +0 -0
  162. /package/client/dist/assets/{addspec-mgPOt5q02.js → addspec-CR8MB5Fr2.js} +0 -0
  163. /package/client/dist/assets/{addspec-CP0HRuxY.js → addspec-DaA-CbTt.js} +0 -0
  164. /package/client/dist/assets/{addspec-DxW6oPsk.js → addspec-DrXqP8ip.js} +0 -0
  165. /package/client/dist/assets/{addspec-CRuvWn8d.js → addspec-NBbcTbLc.js} +0 -0
  166. /package/client/dist/assets/{agents-BpoUaoW92.js → agents-B9pFSJQI2.js} +0 -0
  167. /package/client/dist/assets/{agents-78bTZGZh.js → agents-BHgn--3j.js} +0 -0
  168. /package/client/dist/assets/{agents-BUdEi3-N.js → agents-B_qGaB0F.js} +0 -0
  169. /package/client/dist/assets/{agents-Cev2oFfX.js → agents-CGDn_3kB.js} +0 -0
  170. /package/client/dist/assets/{agents-bvyRLczL.js → agents-CP8_ezYG.js} +0 -0
  171. /package/client/dist/assets/{agents-BLhWCmDS2.js → agents-CV-_xBvX2.js} +0 -0
  172. /package/client/dist/assets/{agents-CWo-KyAB.js → agents-HFCqQH0Z.js} +0 -0
  173. /package/client/dist/assets/{agents-DDtCJ5M02.js → agents-VsyFhGOS2.js} +0 -0
  174. /package/client/dist/assets/{agentstudio-BMQxc_bz.js → agentstudio-BEp7Sb46.js} +0 -0
  175. /package/client/dist/assets/{agentstudio-Dtq3dGYk.js → agentstudio-BOjG6tgJ.js} +0 -0
  176. /package/client/dist/assets/{agentstudio-C_Jq8Tl4.js → agentstudio-Br3WD7Xs.js} +0 -0
  177. /package/client/dist/assets/{agentstudio-sI27TpBu2.js → agentstudio-CuTt3ZGe2.js} +0 -0
  178. /package/client/dist/assets/{agentstudio-CWgXG5W52.js → agentstudio-D2A5peWV2.js} +0 -0
  179. /package/client/dist/assets/{agentstudio-CZm2rOzt.js → agentstudio-Dxe_94PK.js} +0 -0
  180. /package/client/dist/assets/{agentstudio-COZriimI2.js → agentstudio-RfZavULt2.js} +0 -0
  181. /package/client/dist/assets/{agentstudio-jKMTdT70.js → agentstudio-RsxnZtUA.js} +0 -0
  182. /package/client/dist/assets/{aiedit-igmw-ZoZ2.js → aiedit-26p5A_HY2.js} +0 -0
  183. /package/client/dist/assets/{aiedit-6CHdb_iv2.js → aiedit-CV520D4W2.js} +0 -0
  184. /package/client/dist/assets/{aiedit-pc5JDUq1.js → aiedit-CcugGPbS.js} +0 -0
  185. /package/client/dist/assets/{aiedit-eMN4eQPF.js → aiedit-D0Syl-mC.js} +0 -0
  186. /package/client/dist/assets/{aiedit-hknZlly82.js → aiedit-DnBzjlVz2.js} +0 -0
  187. /package/client/dist/assets/{aiedit-B03pD_Na.js → aiedit-DrIjSojo.js} +0 -0
  188. /package/client/dist/assets/{aiedit-DYtnZU9H.js → aiedit-DrfHQnlz.js} +0 -0
  189. /package/client/dist/assets/{aiedit-BtRqRxML.js → aiedit-a-Z-g5iC.js} +0 -0
  190. /package/client/dist/assets/{analytics-C5dpiS0o.js → analytics-4-eskbtX.js} +0 -0
  191. /package/client/dist/assets/{analytics-CR1TJqUi2.js → analytics-4HvXb0Vn2.js} +0 -0
  192. /package/client/dist/assets/{analytics-rmHuJv0V.js → analytics-Bd9rHGjV.js} +0 -0
  193. /package/client/dist/assets/{analytics-BWMUZ2Pq2.js → analytics-Bpe41fnX2.js} +0 -0
  194. /package/client/dist/assets/{analytics-Bj-SmKHa.js → analytics-CpSTNfui.js} +0 -0
  195. /package/client/dist/assets/{analytics-Ce-sPORu.js → analytics-CxDYcBl9.js} +0 -0
  196. /package/client/dist/assets/{analytics-CCprAqk3.js → analytics-DEe5BMz8.js} +0 -0
  197. /package/client/dist/assets/{analytics-DfNdXR0y2.js → analytics-DcOITHla2.js} +0 -0
  198. /package/client/dist/assets/{attachments-BjyMmrwE.js → attachments-CA2f3qL3.js} +0 -0
  199. /package/client/dist/assets/{attachments-DCZOL1RU.js → attachments-CHDbhKeH.js} +0 -0
  200. /package/client/dist/assets/{attachments-rNn1ekRy2.js → attachments-CKAY5aYG2.js} +0 -0
  201. /package/client/dist/assets/{attachments-BgBNSqQd.js → attachments-CgOQ9K-6.js} +0 -0
  202. /package/client/dist/assets/{attachments-DPCmkFZu2.js → attachments-D2jwJXQ42.js} +0 -0
  203. /package/client/dist/assets/{attachments-C9GTTJsx2.js → attachments-DlEQu56g2.js} +0 -0
  204. /package/client/dist/assets/{attachments-Ci6_42Kh.js → attachments-DvxQbBTC.js} +0 -0
  205. /package/client/dist/assets/{attachments-DL-bPy9E.js → attachments-jVdKzAXn.js} +0 -0
  206. /package/client/dist/assets/{auth-Cvsb7-Ek.js → auth-DpZq5p1e.js} +0 -0
  207. /package/client/dist/assets/{browser-Cvg-QHvd2.js → browser-BQFDxrRq.js} +0 -0
  208. /package/client/dist/assets/{browser-rWKj-RR_2.js → browser-BbJ_q_OT2.js} +0 -0
  209. /package/client/dist/assets/{browser-Cc0iDqUi.js → browser-C4VJ5CW12.js} +0 -0
  210. /package/client/dist/assets/{browser-BPkhILSJ.js → browser-Cl-FwPXs.js} +0 -0
  211. /package/client/dist/assets/{browser-CdS6O1Qb.js → browser-DM8G151q.js} +0 -0
  212. /package/client/dist/assets/{browser-FRv8P02E.js → browser-Do2jf8_N.js} +0 -0
  213. /package/client/dist/assets/{browser-BqLqJVVT.js → browser-JsiohNmW.js} +0 -0
  214. /package/client/dist/assets/{browser-BijVYG0Y2.js → browser-kA31eYhu2.js} +0 -0
  215. /package/client/dist/assets/{chat-VMflhzxn.js → chat-0dg5hOo_.js} +0 -0
  216. /package/client/dist/assets/{chat-DdwZjT8K2.js → chat-BMWD_Rx12.js} +0 -0
  217. /package/client/dist/assets/{chat-Bom6bqK3.js → chat-BalviUAD.js} +0 -0
  218. /package/client/dist/assets/{chat-DsbwJhus.js → chat-CJddjN3D2.js} +0 -0
  219. /package/client/dist/assets/{chat-Dnwg-NpQ.js → chat-D5xEtsf3.js} +0 -0
  220. /package/client/dist/assets/{chat-vylxqR0P2.js → chat-Ds8GvCnv.js} +0 -0
  221. /package/client/dist/assets/{chat-BgL3zv6x2.js → chat-SaG-ng1x2.js} +0 -0
  222. /package/client/dist/assets/{chat-Bh3NdgJs.js → chat-n43nZGvn.js} +0 -0
  223. /package/client/dist/assets/{code-thSEptMx.js → code-B62fClk-.js} +0 -0
  224. /package/client/dist/assets/{code-DzC1rMk12.js → code-Bit_z2Xd2.js} +0 -0
  225. /package/client/dist/assets/{code-BRcDlze52.js → code-BotxM43N2.js} +0 -0
  226. /package/client/dist/assets/{code-UPVvmAKM2.js → code-C9U6M5-e2.js} +0 -0
  227. /package/client/dist/assets/{code-BdMy3Ffq.js → code-CIPmDEd0.js} +0 -0
  228. /package/client/dist/assets/{code-DgeHz9CB.js → code-DUe1Bouz.js} +0 -0
  229. /package/client/dist/assets/{code-CwnBPPyA.js → code-F_KjccGf.js} +0 -0
  230. /package/client/dist/assets/{code-CeQ0FZpC.js → code-HDK4P6Rx.js} +0 -0
  231. /package/client/dist/assets/{commands-DkU0qliq.js → commands-BFQZc9AG.js} +0 -0
  232. /package/client/dist/assets/{commands-u-4eEHCN.js → commands-BMkC5-ki.js} +0 -0
  233. /package/client/dist/assets/{commands-CSDs9q_2.js → commands-BllkJauO.js} +0 -0
  234. /package/client/dist/assets/{commands-xHDDGfcP2.js → commands-CWX9WmDX2.js} +0 -0
  235. /package/client/dist/assets/{commands-C4Y3C-PF2.js → commands-CzFSoIiq2.js} +0 -0
  236. /package/client/dist/assets/{commands-CckaBWdV.js → commands-DCJ-R9E4.js} +0 -0
  237. /package/client/dist/assets/{commands-JWMfGDmr.js → commands-KBbhsnw8.js} +0 -0
  238. /package/client/dist/assets/{commands-RHOchN4n2.js → commands-cKTaLwwV2.js} +0 -0
  239. /package/client/dist/assets/{common-DmqBPwZG.js → common-BfQD173j.js} +0 -0
  240. /package/client/dist/assets/{common-DhWTkocg.js → common-CF8iGXrG.js} +0 -0
  241. /package/client/dist/assets/{common-Bwafujnc2.js → common-Cfmwcrl52.js} +0 -0
  242. /package/client/dist/assets/{common-C-vU-j-I.js → common-D2cimsul.js} +0 -0
  243. /package/client/dist/assets/{common-C5HKSbbz.js → common-DH7fjyL1.js} +0 -0
  244. /package/client/dist/assets/{common-CYaT2yHp.js → common-DXrmj6WD.js} +0 -0
  245. /package/client/dist/assets/{common-BM3LFLCE2.js → common-DZbXlmq72.js} +0 -0
  246. /package/client/dist/assets/{common-DiPTT7V_2.js → common-DtXmzX2o2.js} +0 -0
  247. /package/client/dist/assets/{explore-UvW1jEMa.js → explore-BEpIs343.js} +0 -0
  248. /package/client/dist/assets/{explore-CNBzrEPS.js → explore-BO_OinBD.js} +0 -0
  249. /package/client/dist/assets/{explore-C0k8HrkN.js → explore-Bhy7WWXv.js} +0 -0
  250. /package/client/dist/assets/{explore-B7fblXBb2.js → explore-CAc8Qzu32.js} +0 -0
  251. /package/client/dist/assets/{explore-D_mWBYCS2.js → explore-DCZL9gRN2.js} +0 -0
  252. /package/client/dist/assets/{explore-Dahc1lOx.js → explore-DW_YTkQh.js} +0 -0
  253. /package/client/dist/assets/{explore-BHtEpoNN2.js → explore-ELbrSZyQ2.js} +0 -0
  254. /package/client/dist/assets/{explore-BKVV5JSu.js → explore-_XyU5e7R.js} +0 -0
  255. /package/client/dist/assets/{format-command-CuWuRvka.js → format-command-Db1viey1.js} +0 -0
  256. /package/client/dist/assets/{jira-uNH1gETL2.js → jira-B5wszJ1-2.js} +0 -0
  257. /package/client/dist/assets/{jira-BHAKtc4B.js → jira-C5UjxHE4.js} +0 -0
  258. /package/client/dist/assets/{jira-D5efgwp_.js → jira-CQ7mrZde.js} +0 -0
  259. /package/client/dist/assets/{jira-bDs7Im2C.js → jira-DIRkZk9U.js} +0 -0
  260. /package/client/dist/assets/{jira-CPpgA-5z2.js → jira-DTYu3hJi2.js} +0 -0
  261. /package/client/dist/assets/{jira-B3613-gj.js → jira-DeIjwCK3.js} +0 -0
  262. /package/client/dist/assets/{jira-DS7ownDy.js → jira-DtZpokjJ.js} +0 -0
  263. /package/client/dist/assets/{jira-Be369RIR2.js → jira-ZdKb-50F2.js} +0 -0
  264. /package/client/dist/assets/{jobs-CE8qeIYj.js → jobs-BFIdeDTw.js} +0 -0
  265. /package/client/dist/assets/{jobs-BgQzB0Sx2.js → jobs-BJ-Gcao22.js} +0 -0
  266. /package/client/dist/assets/{jobs-1GV2-4zd2.js → jobs-DAGiPVIS2.js} +0 -0
  267. /package/client/dist/assets/{jobs-26F9gGcE.js → jobs-DAU8C_tZ.js} +0 -0
  268. /package/client/dist/assets/{jobs-BqWui9_f.js → jobs-Da0GaaSc.js} +0 -0
  269. /package/client/dist/assets/{jobs-D5sZzgLs.js → jobs-DrYmsKy5.js} +0 -0
  270. /package/client/dist/assets/{jobs-Wh7dn28q2.js → jobs-P3unxtxo2.js} +0 -0
  271. /package/client/dist/assets/{jobs-DzeuASRf.js → jobs-p360zdf1.js} +0 -0
  272. /package/client/dist/assets/{loops-CvOWsi5r.js → loops-Bi4ywYGn.js} +0 -0
  273. /package/client/dist/assets/{loops-BvqQ-bHR2.js → loops-BjK8lmoZ2.js} +0 -0
  274. /package/client/dist/assets/{loops-Brnx4Fji.js → loops-CO6q75W8.js} +0 -0
  275. /package/client/dist/assets/{loops-K5_HXfJa.js → loops-D3wdUkZV.js} +0 -0
  276. /package/client/dist/assets/{loops-D6D3XHjj.js → loops-DDs4KPu_.js} +0 -0
  277. /package/client/dist/assets/{loops-LIgO_-eM.js → loops-DKzYFcq6.js} +0 -0
  278. /package/client/dist/assets/{loops-DJNhp17p2.js → loops-Du-bNVpc2.js} +0 -0
  279. /package/client/dist/assets/{loops-vhJdbcz92.js → loops-DvGbSfxw2.js} +0 -0
  280. /package/client/dist/assets/{mcp-XJNRfxVp2.js → mcp-BG-OCJca2.js} +0 -0
  281. /package/client/dist/assets/{mcp-DtbxuH3V2.js → mcp-CbJ04oFS2.js} +0 -0
  282. /package/client/dist/assets/{mcp-sRmWE8WJ.js → mcp-CrWdq5db.js} +0 -0
  283. /package/client/dist/assets/{mcp-H-gnQUdN.js → mcp-Dd844F5_.js} +0 -0
  284. /package/client/dist/assets/{mcp-Dcl-XxkR.js → mcp-DfmzoT5t.js} +0 -0
  285. /package/client/dist/assets/{mcp-CjVKXAhD2.js → mcp-DyULurYS2.js} +0 -0
  286. /package/client/dist/assets/{mcp-CIH0pH1K.js → mcp-HwBbQDk0.js} +0 -0
  287. /package/client/dist/assets/{mcp-N1nzp8oL.js → mcp-zSJ2TsJj.js} +0 -0
  288. /package/client/dist/assets/{setup-BuQNKztt2.js → setup-1vN48B-m2.js} +0 -0
  289. /package/client/dist/assets/{setup-BwMovM9z2.js → setup-BmJ0Kx16.js} +0 -0
  290. /package/client/dist/assets/{setup-RiUHzq7E.js → setup-D2CmgAro.js} +0 -0
  291. /package/client/dist/assets/{setup-BXpw07r-.js → setup-DQWsu2TR.js} +0 -0
  292. /package/client/dist/assets/{setup-D84y3wx-.js → setup-JeBwoXoM.js} +0 -0
  293. /package/client/dist/assets/{setup-DU0ULX8q2.js → setup-T_ApeQDV2.js} +0 -0
  294. /package/client/dist/assets/{setup-BiduSX9d.js → setup-mXnzww4f.js} +0 -0
  295. /package/client/dist/assets/{setup-7J8TjG4O.js → setup-q0wRHUp1.js} +0 -0
  296. /package/client/dist/assets/{specs-CWtKcVDC.js → specs-BGRKhM8t.js} +0 -0
  297. /package/client/dist/assets/{specs-CBDvEYKv.js → specs-C6J2zrLM.js} +0 -0
  298. /package/client/dist/assets/{specs-D2clrt2c2.js → specs-CT8exGOy2.js} +0 -0
  299. /package/client/dist/assets/{specs-C9Sd7BhH2.js → specs-Ce5y2iFc2.js} +0 -0
  300. /package/client/dist/assets/{specs-CIPr9reA2.js → specs-DZDoZsDq.js} +0 -0
  301. /package/client/dist/assets/{specs-PozUNhqb.js → specs-DbtIzV8a.js} +0 -0
  302. /package/client/dist/assets/{specs-DTplt2XM.js → specs-GkiL-snY.js} +0 -0
  303. /package/client/dist/assets/{specs-CmEZY57v.js → specs-PGIOzMtc.js} +0 -0
  304. /package/client/dist/assets/{terminal-xGbCZJHg.js → terminal-BNSg2lR7.js} +0 -0
  305. /package/client/dist/assets/{terminal-De2Brd23.js → terminal-BTpE7iki.js} +0 -0
  306. /package/client/dist/assets/{terminal-DFPNRLKp.js → terminal-CFt56A5f.js} +0 -0
  307. /package/client/dist/assets/{terminal-BRm03iFA2.js → terminal-Cvabyzm42.js} +0 -0
  308. /package/client/dist/assets/{terminal-DxQWCqSD2.js → terminal-D0z12_sJ2.js} +0 -0
  309. /package/client/dist/assets/{terminal-Z7KsDM_n.js → terminal-DDk_bTw0.js} +0 -0
  310. /package/client/dist/assets/{terminal-cqfd3EDV.js → terminal-DEyt0prF.js} +0 -0
  311. /package/client/dist/assets/{terminal-WSAvHXoU.js → terminal-a6Uq8B0p.js} +0 -0
  312. /package/client/dist/assets/{tickets-CkfbISuK.js → tickets-BHv3ewvq.js} +0 -0
  313. /package/client/dist/assets/{tickets-C3b8cHhd2.js → tickets-BIxU86j12.js} +0 -0
  314. /package/client/dist/assets/{tickets-SuX_HkK4.js → tickets-C2BhwzwR.js} +0 -0
  315. /package/client/dist/assets/{tickets-DTHo9c3C.js → tickets-D1IeX2EW.js} +0 -0
  316. /package/client/dist/assets/{tickets-BoXom7l8.js → tickets-DZut8Ryt.js} +0 -0
  317. /package/client/dist/assets/{tickets-CWYFvx1W2.js → tickets-LfK6LzKl2.js} +0 -0
  318. /package/client/dist/assets/{tickets-CFXnJsYF.js → tickets-dQfsc7Ph.js} +0 -0
  319. /package/client/dist/assets/{tickets-B7zT4IRf.js → tickets-sVun0I6v.js} +0 -0
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.setHeadroomRoutingState = setHeadroomRoutingState;
4
+ exports.getHeadroomRoutingState = getHeadroomRoutingState;
5
+ exports.providerForBinary = providerForBinary;
6
+ exports.applyHeadroomEnvForBinary = applyHeadroomEnvForBinary;
7
+ exports.withHeadroomSpawnEnv = withHeadroomSpawnEnv;
8
+ const DEFAULT_PORT = 8787;
9
+ let routingState = {
10
+ port: DEFAULT_PORT,
11
+ activeProviders: {},
12
+ };
13
+ function setHeadroomRoutingState(next) {
14
+ routingState = {
15
+ port: Number.isInteger(next.port) ? next.port : DEFAULT_PORT,
16
+ activeProviders: { ...next.activeProviders },
17
+ };
18
+ }
19
+ function getHeadroomRoutingState() {
20
+ return {
21
+ port: routingState.port,
22
+ activeProviders: { ...routingState.activeProviders },
23
+ };
24
+ }
25
+ function providerForBinary(binary) {
26
+ const name = binary.toLowerCase().replace(/\.cmd$|\.exe$/, '');
27
+ if (name === 'codex')
28
+ return 'codex';
29
+ if (name === 'claude')
30
+ return 'claude';
31
+ return null;
32
+ }
33
+ function applyHeadroomEnvForBinary(binary, env) {
34
+ const provider = providerForBinary(binary);
35
+ if (!provider || !routingState.activeProviders[provider])
36
+ return env ?? process.env;
37
+ const base = `http://127.0.0.1:${routingState.port}`;
38
+ const merged = { ...(env ?? process.env) };
39
+ if (provider === 'codex') {
40
+ merged.OPENAI_BASE_URL = `${base}/v1`;
41
+ }
42
+ else if (provider === 'claude') {
43
+ merged.ANTHROPIC_BASE_URL = base;
44
+ }
45
+ merged.HEADROOM_PORT = String(routingState.port);
46
+ return merged;
47
+ }
48
+ function withHeadroomSpawnEnv(binary, options = {}) {
49
+ return {
50
+ ...options,
51
+ env: applyHeadroomEnvForBinary(binary, options.env),
52
+ };
53
+ }
@@ -29,10 +29,13 @@ const agent_chat_router_1 = require("./agent-chat-router");
29
29
  const agent_chat_registry_1 = require("./agent-chat-registry");
30
30
  const browser_playwright_1 = require("./browser-playwright");
31
31
  const telemetry_receiver_1 = require("./telemetry-receiver");
32
+ const headroom_manager_1 = require("./headroom-manager");
33
+ const global_plugins_router_1 = require("./global-plugins-router");
32
34
  const telemetry_compactor_1 = require("./telemetry-compactor");
33
35
  const framework_manager_1 = require("./framework-manager");
34
36
  const artifact_registry_1 = require("./artifact-registry");
35
37
  const path_resolver_1 = require("./path-resolver");
38
+ const dev_ports_1 = require("./dev-ports");
36
39
  // Side-effect import: registers every bundled ProviderAdapter (claude, codex,
37
40
  // future providers) so `getAdapter`/`hasAdapter`/`listAdapters` are populated
38
41
  // before any manager constructs a project context. See
@@ -85,12 +88,7 @@ if (parentPidArg) {
85
88
  }
86
89
  }
87
90
  // ─── Parse CLI args ───────────────────────────────────────────────────────────
88
- let port = 4200;
89
- for (let i = 2; i < process.argv.length; i++) {
90
- if (process.argv[i] === '--port' && process.argv[i + 1]) {
91
- port = parseInt(process.argv[++i], 10);
92
- }
93
- }
91
+ const port = (0, dev_ports_1.resolveServerPort)(process.argv);
94
92
  // ─── PID file management ──────────────────────────────────────────────────────
95
93
  const PID_DIR = path_1.default.join(os_1.default.homedir(), '.specrails');
96
94
  const PID_FILE = path_1.default.join(PID_DIR, 'manager.pid');
@@ -541,6 +539,11 @@ function applyPtyWsRateLimiting(ws) {
541
539
  mcpManager.start().catch((err) => console.error('[mcp] boot start failed:', err));
542
540
  }
543
541
  // ─── App-global agent chat (drives the app via its own MCP) ────────────────
542
+ const headroomManager = new headroom_manager_1.HeadroomManager(registry.desktopDb, broadcast, () => registry.installedProvidersUnion().filter((provider) => provider === 'codex' || provider === 'claude'));
543
+ app.use('/api/global-plugins', (0, global_plugins_router_1.createGlobalPluginsRouter)(headroomManager));
544
+ headroomManager.startActiveProxyOnBoot().catch((err) => {
545
+ console.error('[headroom] boot start failed:', err);
546
+ });
544
547
  const agentChatManager = new agent_chat_manager_1.AgentChatManager(broadcast, registry.desktopDb, port, registry);
545
548
  _agentChatManager = agentChatManager;
546
549
  // Publish the instance to the process-wide registry so the rails layer can
@@ -149,6 +149,9 @@ heading inside the description); \`labels\`; \`priority\`. Spec content is Engli
149
149
  - \`specrails_jobs(spawn, command)\` bypasses rails and enqueues an arbitrary
150
150
  slash-command job (e.g. \`/specrails:implement #5 --yes\`); \`queue\`, \`pause\`,
151
151
  \`resume\`, \`reorder\`, \`priority\` manage the queue.
152
+ - Long-running shell commands launched with \`specrails_jobs(background_start)\`
153
+ create chat chips; use \`specrails_jobs(background_logs, pid)\` to read bounded
154
+ stdout/stderr tail when a chip exits or fails.
152
155
 
153
156
  ## Async results (IMPORTANT)
154
157
 
@@ -18,6 +18,7 @@ const setup_1 = require("./setup");
18
18
  const analytics_1 = require("./analytics");
19
19
  const git_1 = require("./git");
20
20
  const support_1 = require("./support");
21
+ const env_1 = require("./env");
21
22
  /**
22
23
  * The full MCP tool catalog. Domain-facade tools each expose an `action` enum
23
24
  * and call the app's REST API over loopback (see apiCall). Meta tools receive a
@@ -42,6 +43,7 @@ function buildToolSpecs() {
42
43
  specs.push(...(0, setup_1.setupTools)());
43
44
  specs.push(...(0, analytics_1.analyticsTools)());
44
45
  specs.push(...(0, git_1.gitTools)());
46
+ specs.push(...(0, env_1.envTools)());
45
47
  specs.push(...(0, support_1.supportTools)());
46
48
  // Meta last (needs the full list for search/describe)
47
49
  specs.push(...(0, meta_1.metaTools)(() => specs));
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.envTools = envTools;
4
+ const zod_1 = require("zod");
5
+ const types_1 = require("./types");
6
+ const worktree_env_discovery_1 = require("../../worktree-env-discovery");
7
+ function uniq(names) {
8
+ const out = [];
9
+ for (const v of names) {
10
+ if (typeof v !== 'string')
11
+ continue;
12
+ const name = v.trim();
13
+ if (name && !out.includes(name))
14
+ out.push(name);
15
+ }
16
+ return out;
17
+ }
18
+ function envTools() {
19
+ return [
20
+ {
21
+ name: 'specrails_env',
22
+ title: 'Project worktree environment',
23
+ description: 'Read, scan, and configure the per-project environment variable NAME allowlist used for rail jobs and isolated loop worktrees. ' +
24
+ 'Values are never read or stored by this tool; runs read them from the server environment and can recover configured names from the login shell when the desktop process is missing them. scan inspects safe repo config files such as package.json, .npmrc, .yarnrc.yml and pnpm-workspace.yaml for ${VAR} / process.env.VAR references and private scoped package auth hints. ' +
25
+ 'Actions: get (read current names), scan (discover candidates), set (write names), auto_configure (scan then merge discovered names into settings).',
26
+ hintTier: 'read',
27
+ tier: (args) => (args.action === 'set' || args.action === 'auto_configure' ? 'write' : 'read'),
28
+ inputSchema: {
29
+ action: zod_1.z.enum(['get', 'scan', 'set', 'auto_configure']).describe('Operation'),
30
+ projectId: zod_1.z.string().optional().describe('Project id (defaults to the active project)'),
31
+ names: zod_1.z.array(zod_1.z.string()).optional().describe('set: env var names to configure'),
32
+ merge: zod_1.z.boolean().optional().describe('set: merge with existing names instead of replacing them (default true)'),
33
+ },
34
+ async handler(ctx, args) {
35
+ const action = args.action;
36
+ const base = (0, types_1.projectPath)(ctx, args.projectId);
37
+ if (action === 'get') {
38
+ const settings = await (0, types_1.apiCall)(ctx, 'GET', `${base}/settings`);
39
+ return { names: Array.isArray(settings.worktreeEnvPassthrough) ? settings.worktreeEnvPassthrough : [] };
40
+ }
41
+ if (action === 'scan') {
42
+ const project = (0, types_1.requireProject)(ctx, args.projectId).project;
43
+ return (0, worktree_env_discovery_1.scanWorktreeEnvRequirements)(project.path);
44
+ }
45
+ if (action === 'set') {
46
+ const incoming = uniq(Array.isArray(args.names) ? args.names : []);
47
+ if (incoming.length === 0)
48
+ throw new Error('set requires at least one env var name in "names".');
49
+ const merge = args.merge !== false;
50
+ const current = await (0, types_1.apiCall)(ctx, 'GET', `${base}/settings`);
51
+ const existing = Array.isArray(current.worktreeEnvPassthrough) ? uniq(current.worktreeEnvPassthrough) : [];
52
+ const next = merge ? uniq([...existing, ...incoming]) : incoming;
53
+ const res = await (0, types_1.apiCall)(ctx, 'PATCH', `${base}/settings`, { worktreeEnvPassthrough: next });
54
+ return { ok: true, names: res.settings?.worktreeEnvPassthrough ?? next, changed: next.filter((n) => !existing.includes(n)) };
55
+ }
56
+ if (action === 'auto_configure') {
57
+ const project = (0, types_1.requireProject)(ctx, args.projectId).project;
58
+ const scan = (0, worktree_env_discovery_1.scanWorktreeEnvRequirements)(project.path);
59
+ const discovered = scan.candidates.map((c) => c.name);
60
+ const current = await (0, types_1.apiCall)(ctx, 'GET', `${base}/settings`);
61
+ const existing = Array.isArray(current.worktreeEnvPassthrough) ? uniq(current.worktreeEnvPassthrough) : [];
62
+ if (discovered.length === 0)
63
+ return { ok: true, names: existing, changed: [], scan };
64
+ const next = uniq([...existing, ...discovered]);
65
+ const res = await (0, types_1.apiCall)(ctx, 'PATCH', `${base}/settings`, { worktreeEnvPassthrough: next });
66
+ return { ok: true, names: res.settings?.worktreeEnvPassthrough ?? next, changed: next.filter((n) => !existing.includes(n)), scan };
67
+ }
68
+ throw new Error(`Unknown action "${action}".`);
69
+ },
70
+ },
71
+ ];
72
+ }
@@ -1,8 +1,52 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.jobsTools = jobsTools;
4
7
  const zod_1 = require("zod");
5
8
  const types_1 = require("./types");
9
+ const path_1 = __importDefault(require("path"));
10
+ const transient_children_1 = require("../../transient-children");
11
+ const loop_runs_store_1 = require("../../loop-runs-store");
12
+ function resolveBackgroundCwd(projectRoot, cwd) {
13
+ const resolved = path_1.default.resolve(projectRoot, cwd && cwd.trim() ? cwd : '.');
14
+ const root = path_1.default.resolve(projectRoot);
15
+ if (resolved !== root && !resolved.startsWith(root + path_1.default.sep)) {
16
+ throw new Error('background_start cwd must stay within the selected project.');
17
+ }
18
+ return resolved;
19
+ }
20
+ function backgroundHooks(broadcast) {
21
+ return {
22
+ onStarted: (process) => broadcast({
23
+ type: 'background_process.started',
24
+ process,
25
+ projectId: process.projectId,
26
+ timestamp: new Date().toISOString(),
27
+ }),
28
+ onExited: (process) => broadcast({
29
+ type: 'background_process.exited',
30
+ process,
31
+ projectId: process.projectId,
32
+ timestamp: new Date().toISOString(),
33
+ }),
34
+ };
35
+ }
36
+ function backgroundStartBusyReason(projectCtx) {
37
+ const activeJobId = typeof projectCtx.queueManager?.getActiveJobId === 'function' ? projectCtx.queueManager.getActiveJobId() : null;
38
+ if (activeJobId)
39
+ return `job ${activeJobId} is still running`;
40
+ try {
41
+ const runningLoops = (0, loop_runs_store_1.listRunningLoopRuns)(projectCtx.db, projectCtx.project.id);
42
+ if (runningLoops.length > 0)
43
+ return `loop run ${runningLoops[0].id} is still running`;
44
+ }
45
+ catch {
46
+ // Some unit harnesses stub the DB narrowly; absence of loop metadata is not a blocker.
47
+ }
48
+ return null;
49
+ }
6
50
  /**
7
51
  * Jobs domain facade. Maps every in-scope jobs/queue action to its REST
8
52
  * endpoint on the loopback API (server/project-router-jobs.ts, plus the
@@ -20,6 +64,7 @@ function jobsTools() {
20
64
  title: 'Jobs & Queue',
21
65
  description: 'Manage a project\'s AI-pipeline job queue and individual jobs. ' +
22
66
  'Actions: list, get, queue, spawn (ai-spawn — enqueues an arbitrary slash-command job, returns {jobId,position}, async; from the in-app agent chat the engine defaults to your conversation\'s provider — pass aiEngine to override), ' +
67
+ 'background_start (operate-level shell command tied to the current agent chat; requires confirmed:true after explicit user confirmation before use), background_logs, background_kill, ' +
23
68
  'cancel (destructive — cancels a running/queued job or deletes a terminal one), ' +
24
69
  'purge (destructive — bulk-delete persisted job rows in a date range), ' +
25
70
  'pause / resume (queue), reorder (queued-job order), priority (change a queued job\'s priority), ' +
@@ -34,7 +79,7 @@ function jobsTools() {
34
79
  const action = a.action;
35
80
  if (['cancel', 'purge'].includes(action))
36
81
  return 'destructive';
37
- if (['spawn', 'interactive_turn'].includes(action))
82
+ if (['spawn', 'interactive_turn', 'background_start', 'background_kill'].includes(action))
38
83
  return 'ai-spawn';
39
84
  if (['pause', 'resume', 'reorder', 'priority', 'finalize'].includes(action))
40
85
  return 'write';
@@ -64,6 +109,9 @@ function jobsTools() {
64
109
  'default_spec_model',
65
110
  'interactive_turn',
66
111
  'finalize',
112
+ 'background_start',
113
+ 'background_logs',
114
+ 'background_kill',
67
115
  ])
68
116
  .describe('Operation to perform'),
69
117
  projectId: zod_1.z.string().optional().describe('Project id (defaults to the active project)'),
@@ -73,7 +121,7 @@ function jobsTools() {
73
121
  .optional()
74
122
  .describe('Job id (for get / cancel / priority / diagnostic / interactive_turn / finalize)'),
75
123
  // ── list / export pagination + filters ──
76
- limit: zod_1.z.number().optional().describe('Page size for list (1-200, default 50) / activity (1-100, default 50)'),
124
+ limit: zod_1.z.number().optional().describe('Page size for list (1-200, default 50) / activity (1-100, default 50); background_logs returns the last N buffered log lines'),
77
125
  offset: zod_1.z.number().optional().describe('Page offset for list'),
78
126
  status: zod_1.z.string().optional().describe('Filter by job status (list)'),
79
127
  from: zod_1.z.string().optional().describe('ISO date lower bound (list / export / purge)'),
@@ -99,6 +147,11 @@ function jobsTools() {
99
147
  .describe('reorder: exact set of currently-queued job ids in the desired order; compare: exactly 2 job ids'),
100
148
  // ── interactive_turn ──
101
149
  text: zod_1.z.string().optional().describe('Prompt text for interactive_turn'),
150
+ chatId: zod_1.z.string().optional().describe('Agent chat conversation id for background_start/background_logs/background_kill ownership; defaults to the in-app agent conversation that called the tool'),
151
+ cwd: zod_1.z.string().optional().describe('Optional cwd for background_start; resolved inside the selected project root'),
152
+ pid: zod_1.z.number().optional().describe('Background process pid for background_logs/background_kill'),
153
+ confirmed: zod_1.z.boolean().optional().describe('background_start only: must be true after explicit user confirmation for this exact command'),
154
+ allowWhileBusy: zod_1.z.boolean().optional().describe('background_start only: override active job/loop guard after explicit user confirmation'),
102
155
  // ── export ──
103
156
  format: zod_1.z.enum(['json', 'csv']).optional().describe('Export format (default json)'),
104
157
  // ── default_spec_model ──
@@ -270,6 +323,61 @@ function jobsTools() {
270
323
  throw new Error('finalize requires a "jobId".');
271
324
  return (0, types_1.apiCall)(ctx, 'POST', `${base}/jobs/${encodeURIComponent(id)}/finalize`);
272
325
  }
326
+ case 'background_start': {
327
+ const command = args.command;
328
+ if (!command || !command.trim())
329
+ throw new Error('background_start requires a "command".');
330
+ if (args.confirmed !== true)
331
+ throw new Error('background_start requires confirmed:true after explicit user confirmation for this exact command.');
332
+ const chatId = args.chatId ?? ctx.originConversationId ?? undefined;
333
+ if (!chatId || !chatId.trim())
334
+ throw new Error('background_start requires a "chatId".');
335
+ const projectCtx = (0, types_1.requireProject)(ctx, args.projectId);
336
+ const busyReason = backgroundStartBusyReason(projectCtx);
337
+ if (busyReason && args.allowWhileBusy !== true) {
338
+ throw new Error(`background_start refused because ${busyReason}. Wait for it to finish, or pass allowWhileBusy:true only after explicit user confirmation.`);
339
+ }
340
+ const cwd = resolveBackgroundCwd(projectCtx.project.path, args.cwd);
341
+ const process = (0, transient_children_1.startBackgroundProcess)(command.trim(), cwd, chatId.trim(), projectCtx.project.id, backgroundHooks(ctx.broadcast));
342
+ return { ok: true, process };
343
+ }
344
+ case 'background_kill': {
345
+ const pid = args.pid;
346
+ if (typeof pid !== 'number' || !Number.isFinite(pid))
347
+ throw new Error('background_kill requires a numeric "pid".');
348
+ const chatId = args.chatId ?? ctx.originConversationId ?? undefined;
349
+ if (!chatId || !chatId.trim())
350
+ throw new Error('background_kill requires a "chatId".');
351
+ const projectCtx = (0, types_1.requireProject)(ctx, args.projectId);
352
+ const killed = (0, transient_children_1.killOwnedBackgroundProcess)(pid, { projectId: projectCtx.project.id, chatId: chatId.trim() });
353
+ if (!killed)
354
+ throw new Error('background_kill requires a registered pid in the same project/chat.');
355
+ return { ok: true, pid, status: 'killing' };
356
+ }
357
+ case 'background_logs': {
358
+ const pid = args.pid;
359
+ if (typeof pid !== 'number' || !Number.isFinite(pid))
360
+ throw new Error('background_logs requires a numeric "pid".');
361
+ const chatId = args.chatId ?? ctx.originConversationId ?? undefined;
362
+ if (!chatId || !chatId.trim())
363
+ throw new Error('background_logs requires a "chatId".');
364
+ const projectCtx = (0, types_1.requireProject)(ctx, args.projectId);
365
+ const limit = typeof args.limit === 'number' && Number.isFinite(args.limit) ? args.limit : undefined;
366
+ const logs = (0, transient_children_1.getBackgroundProcessLogs)(pid, {
367
+ projectId: projectCtx.project.id,
368
+ chatId: chatId.trim(),
369
+ ...(limit !== undefined ? { limit } : {}),
370
+ });
371
+ if (!logs)
372
+ throw new Error('background_logs requires a registered pid in the same project/chat; logs may have expired.');
373
+ return {
374
+ ok: true,
375
+ ...logs,
376
+ hint: logs.lines.length > 0
377
+ ? 'Inspect stdout/stderr to explain why the background process exited or failed.'
378
+ : 'No stdout/stderr lines have been captured yet; the process may not have emitted newline-terminated output.',
379
+ };
380
+ }
273
381
  default:
274
382
  throw new Error(`Unknown action "${action}".`);
275
383
  }
@@ -11,6 +11,8 @@ exports.resolveStartupPath = resolveStartupPath;
11
11
  exports.parseLoginShellOutput = parseLoginShellOutput;
12
12
  exports.augmentPathFromLoginShell = augmentPathFromLoginShell;
13
13
  exports.parseLoginShellEnv = parseLoginShellEnv;
14
+ exports.augmentEnvFromLoginShell = augmentEnvFromLoginShell;
15
+ exports.augmentEnvFromLoginShellSync = augmentEnvFromLoginShellSync;
14
16
  exports.augmentAuthEnvFromLoginShell = augmentAuthEnvFromLoginShell;
15
17
  exports.getPathDiagnostic = getPathDiagnostic;
16
18
  exports.__resetPathResolverForTest = __resetPathResolverForTest;
@@ -103,7 +105,14 @@ function resolveBundledBinDirs(runtimesPath) {
103
105
  else if (fileExists(path_1.default.join(runtimesPath, 'git', 'bin', 'git'))) {
104
106
  gitBinDir = path_1.default.join(runtimesPath, 'git', 'bin');
105
107
  }
106
- return { nodeBinDir, gitBinDir };
108
+ const uvBinDir = isWin
109
+ ? (fileExists(path_1.default.join(runtimesPath, 'uv', 'uv.exe')) ? path_1.default.join(runtimesPath, 'uv')
110
+ : fileExists(path_1.default.join(runtimesPath, 'uv', 'bin', 'uv.exe')) ? path_1.default.join(runtimesPath, 'uv', 'bin')
111
+ : null)
112
+ : (fileExists(path_1.default.join(runtimesPath, 'uv', 'bin', 'uv')) ? path_1.default.join(runtimesPath, 'uv', 'bin')
113
+ : fileExists(path_1.default.join(runtimesPath, 'uv', 'uv')) ? path_1.default.join(runtimesPath, 'uv')
114
+ : null);
115
+ return { nodeBinDir, gitBinDir, uvBinDir };
107
116
  }
108
117
  /**
109
118
  * Bin dir of the bundled GitHub CLI (`runtimes/gh/bin/gh[.exe]`), or null.
@@ -264,7 +273,7 @@ function resolveStartupPathBase() {
264
273
  if (process.env.SPECRAILS_IS_DESKTOP === '1') {
265
274
  const runtimesPath = process.env.SPECRAILS_BUNDLED_RUNTIMES_PATH;
266
275
  if (runtimesPath) {
267
- const { nodeBinDir, gitBinDir } = resolveBundledBinDirs(runtimesPath);
276
+ const { nodeBinDir, gitBinDir, uvBinDir } = resolveBundledBinDirs(runtimesPath);
268
277
  // Activate the bundle only when BOTH node and git are present. A partial
269
278
  // bundle (one tool present, the other missing — a botched extraction) is
270
279
  // treated as NOT active so the full system fallback (fast-path + login-shell)
@@ -273,7 +282,7 @@ function resolveStartupPathBase() {
273
282
  if (nodeBinDir && gitBinDir) {
274
283
  const inherited = splitPath(process.env.PATH);
275
284
  const inheritedSet = new Set(inherited);
276
- const bundledDirs = [nodeBinDir, gitBinDir].filter((d) => !inheritedSet.has(d));
285
+ const bundledDirs = [nodeBinDir, gitBinDir, uvBinDir].filter((d) => !!d && !inheritedSet.has(d));
277
286
  bundledDirs.forEach((d) => inheritedSet.add(d));
278
287
  // Provider CLIs (claude/codex/gemini) are NEVER bundled — they always
279
288
  // come from the system. On Windows their `.cmd` shims live in
@@ -464,6 +473,24 @@ exports.AUTH_ENV_VARS = [
464
473
  'GOOGLE_CLOUD_LOCATION',
465
474
  'GOOGLE_APPLICATION_CREDENTIALS',
466
475
  ];
476
+ const LOGIN_SHELL_ENV_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
477
+ const loginShellEnvProbeCache = new Set();
478
+ function normalizeLoginShellEnvNames(names) {
479
+ const out = [];
480
+ for (const raw of names) {
481
+ const name = String(raw).trim();
482
+ if (!LOGIN_SHELL_ENV_NAME_RE.test(name) || out.includes(name))
483
+ continue;
484
+ out.push(name);
485
+ }
486
+ return out;
487
+ }
488
+ function buildLoginShellEnvCommand(names) {
489
+ // `printf` reuses POSIX positional `$VAR` expansion (works in sh/bash/zsh).
490
+ const fmt = ENV_BEGIN + names.map((v) => `${v}=%s\n`).join('') + ENV_END;
491
+ const refs = names.map((v) => `"$${v}"`).join(' ');
492
+ return `printf '${fmt}' ${refs}`;
493
+ }
467
494
  /** Parse the `KEY=value` block emitted between the env sentinels. */
468
495
  function parseLoginShellEnv(stdout) {
469
496
  const begin = stdout.indexOf(ENV_BEGIN);
@@ -486,27 +513,23 @@ function parseLoginShellEnv(stdout) {
486
513
  }
487
514
  return out;
488
515
  }
489
- /**
490
- * Spawn the user's login shell once and backfill any provider auth env vars it
491
- * exposes into `process.env` (only when not already set — never override an
492
- * explicit value). POSIX-only and runs REGARDLESS of bundle state (unlike the
493
- * PATH augmentation, which is skipped when the bundle is active): macOS desktop
494
- * builds bundle node/git, so the PATH login-shell probe is skipped, yet we still
495
- * need to recover the gemini key. Async, fire-and-forget — must not block
496
- * startup. No-op on Windows (GUI inherits user env) and in tests.
497
- */
498
- async function augmentAuthEnvFromLoginShell(opts = {}) {
516
+ /** Backfill arbitrary, pre-validated environment variable names from the user's
517
+ * login shell into `process.env` (only when not already set — never override an
518
+ * explicit value). POSIX-only and runs regardless of bundle state, so macOS GUI
519
+ * launches can recover tokens exported in `.zshrc`/`.bashrc` without hardcoding
520
+ * every possible package-manager/provider variable. No-op on Windows and tests. */
521
+ async function augmentEnvFromLoginShell(names, opts = {}) {
499
522
  if (process.platform === 'win32')
500
523
  return;
501
524
  if (process.env.NODE_ENV === 'test' || process.env.VITEST === 'true')
502
525
  return;
526
+ const wanted = normalizeLoginShellEnvNames(names).filter((name) => !process.env[name]);
527
+ if (wanted.length === 0)
528
+ return;
503
529
  const spawnFn = opts.spawnFn ?? child_process_1.spawn;
504
530
  const timeoutMs = opts.timeoutMs ?? LOGIN_SHELL_TIMEOUT_MS;
505
531
  const shell = process.env.SHELL || '/bin/sh';
506
- // `printf` reuses POSIX positional `$VAR` expansion (works in sh/bash/zsh).
507
- const fmt = ENV_BEGIN + exports.AUTH_ENV_VARS.map((v) => `${v}=%s\n`).join('') + ENV_END;
508
- const refs = exports.AUTH_ENV_VARS.map((v) => `"$${v}"`).join(' ');
509
- const command = `printf '${fmt}' ${refs}`;
532
+ const command = buildLoginShellEnvCommand(wanted);
510
533
  await new Promise((resolve) => {
511
534
  let child;
512
535
  try {
@@ -536,7 +559,7 @@ async function augmentAuthEnvFromLoginShell(opts = {}) {
536
559
  child.on('error', finish);
537
560
  child.on('close', () => {
538
561
  const recovered = parseLoginShellEnv(stdout);
539
- for (const key of exports.AUTH_ENV_VARS) {
562
+ for (const key of wanted) {
540
563
  const val = recovered[key];
541
564
  // Backfill only when unset/empty — never clobber an explicit value.
542
565
  if (val && !process.env[key])
@@ -546,6 +569,48 @@ async function augmentAuthEnvFromLoginShell(opts = {}) {
546
569
  });
547
570
  });
548
571
  }
572
+ /** Synchronous, cached variant for spawn-time env assembly. It probes each
573
+ * missing name at most once per process to keep loops from paying the login
574
+ * shell timeout on every step. */
575
+ function augmentEnvFromLoginShellSync(names, opts = {}) {
576
+ if (process.platform === 'win32')
577
+ return;
578
+ if (process.env.NODE_ENV === 'test' || process.env.VITEST === 'true')
579
+ return;
580
+ const wanted = normalizeLoginShellEnvNames(names).filter((name) => !process.env[name] && !loginShellEnvProbeCache.has(name));
581
+ if (wanted.length === 0)
582
+ return;
583
+ for (const name of wanted)
584
+ loginShellEnvProbeCache.add(name);
585
+ const spawnSyncFn = opts.spawnSyncFn ?? child_process_1.spawnSync;
586
+ const timeoutMs = opts.timeoutMs ?? LOGIN_SHELL_TIMEOUT_MS;
587
+ const shell = process.env.SHELL || '/bin/sh';
588
+ const command = buildLoginShellEnvCommand(wanted);
589
+ let res;
590
+ try {
591
+ res = spawnSyncFn(shell, ['-l', '-i', '-c', command], {
592
+ encoding: 'utf8',
593
+ stdio: ['ignore', 'pipe', 'pipe'],
594
+ timeout: timeoutMs,
595
+ });
596
+ }
597
+ catch {
598
+ return;
599
+ }
600
+ if (res.error)
601
+ return;
602
+ const stdout = typeof res.stdout === 'string' ? res.stdout : res.stdout?.toString('utf8') ?? '';
603
+ const recovered = parseLoginShellEnv(stdout);
604
+ for (const key of wanted) {
605
+ const val = recovered[key];
606
+ if (val && !process.env[key])
607
+ process.env[key] = val;
608
+ }
609
+ }
610
+ /** Startup backfill for the built-in provider auth variables. */
611
+ async function augmentAuthEnvFromLoginShell(opts = {}) {
612
+ await augmentEnvFromLoginShell(exports.AUTH_ENV_VARS, opts);
613
+ }
549
614
  function mergeLoginShellPath(rawPath) {
550
615
  const current = splitPath(process.env.PATH);
551
616
  const currentSet = new Set(current);
@@ -582,4 +647,5 @@ function __resetPathResolverForTest() {
582
647
  diagnostic = { pathSegments: [], pathSources: [], loginShellStatus: 'skipped' };
583
648
  warnedLoginShell = false;
584
649
  bundledRuntimesActive = false;
650
+ loginShellEnvProbeCache.clear();
585
651
  }
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveWorktreeEnvPassthrough = resolveWorktreeEnvPassthrough;
4
+ exports.applyWorktreeEnvPassthrough = applyWorktreeEnvPassthrough;
5
+ const db_1 = require("./db");
6
+ const path_resolver_1 = require("./path-resolver");
7
+ /** Resolve the per-project env passthrough overlay for a spawn.
8
+ *
9
+ * The project setting stores names only. Values are read from `sourceEnv` at
10
+ * spawn time so credentials can rotate without touching SQLite. When the
11
+ * source is `process.env`, missing configured names get one bounded login-shell
12
+ * recovery attempt first; unresolved names are still ignored instead of
13
+ * blocking the run.
14
+ */
15
+ function resolveWorktreeEnvPassthrough(db, sourceEnv = process.env) {
16
+ const out = {};
17
+ const names = (0, db_1.getProjectSettings)(db).worktreeEnvPassthrough;
18
+ if (sourceEnv === process.env)
19
+ (0, path_resolver_1.augmentEnvFromLoginShellSync)(names);
20
+ for (const name of names) {
21
+ const value = sourceEnv[name];
22
+ if (value !== undefined)
23
+ out[name] = value;
24
+ }
25
+ return out;
26
+ }
27
+ function applyWorktreeEnvPassthrough(db, env, sourceEnv = process.env) {
28
+ const passthrough = resolveWorktreeEnvPassthrough(db, sourceEnv);
29
+ return Object.keys(passthrough).length === 0 ? env : { ...env, ...passthrough };
30
+ }
@@ -29,6 +29,7 @@ const transient_children_1 = require("./transient-children");
29
29
  const telemetry_receiver_1 = require("./telemetry-receiver");
30
30
  const artifact_registry_1 = require("./artifact-registry");
31
31
  const workspace_resolution_1 = require("./workspace-resolution");
32
+ const project_env_1 = require("./project-env");
32
33
  const workspace_manager_1 = require("./workspace-manager");
33
34
  const ticket_store_1 = require("./ticket-store");
34
35
  const jira_sync_manager_1 = require("./jira/jira-sync-manager");
@@ -689,9 +690,10 @@ class ProjectRegistry {
689
690
  // `${ENV:-legacy}` defaults resolve inside the worktree, where only tracked
690
691
  // files exist — still reads the real project state. SPECRAILS_REPO_DIR stays
691
692
  // per-run (the worktree for isolated runs). Legacy projects keep process.env
692
- // byte-identical. See resolveLoopBaseEnv.
693
+ // byte-identical except for the explicit per-project passthrough overlay.
694
+ // See resolveLoopBaseEnv.
693
695
  const loopRunManager = new loop_run_manager_1.LoopRunManager(db, boundBroadcast, (0, loop_executors_1.createLoopExecutors)({
694
- env: () => (0, workspace_resolution_1.resolveLoopBaseEnv)({ slug: project.slug, path: project.path }),
696
+ env: () => (0, workspace_resolution_1.resolveLoopBaseEnv)({ slug: project.slug, path: project.path }, undefined, (0, project_env_1.applyWorktreeEnvPassthrough)(db, process.env)),
695
697
  }));
696
698
  const getTicketSpec = (ticketId) => {
697
699
  try {