specrails-desktop 2.26.2 → 2.27.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.
- package/client/dist/assets/{ActivityFeedPage-bDe4VitF.js → ActivityFeedPage-BlkNVXCv.js} +1 -1
- package/client/dist/assets/AgentBrowserCapture-76Ri2QR1.js +1 -0
- package/client/dist/assets/AgentIntegrationsModal-D9hVgaBR.js +1 -0
- package/client/dist/assets/{AgentModeCodePane-Ci39DcOo.js → AgentModeCodePane-BUR_QxqR.js} +2 -2
- package/client/dist/assets/AgentModeJobsPane-CnRXelns.js +1 -0
- package/client/dist/assets/AgentsPage-DEt9vpzh.js +86 -0
- package/client/dist/assets/{AnalyticsPage-Da21wWWw.js → AnalyticsPage-C_eT3I3i.js} +1 -1
- package/client/dist/assets/{BarChart-BiLTq3uC.js → BarChart-DuzYxiJ6.js} +1 -1
- package/client/dist/assets/CodePage-72-FLBNw.js +2 -0
- package/client/dist/assets/{DesktopAnalyticsPage-COkzgMmd.js → DesktopAnalyticsPage-BSeBkF2n.js} +1 -1
- package/client/dist/assets/{DocsDialog-PZHMN18B.js → DocsDialog-CGzqDyN3.js} +2 -2
- package/client/dist/assets/DocsPage-BB3VGFM9.js +11 -0
- package/client/dist/assets/{ExportDropdown-DDhJH-Jr.js → ExportDropdown-IehskH-7.js} +1 -1
- package/client/dist/assets/IntegrationsPage-X8bLyuDk.js +3 -0
- package/client/dist/assets/InteractiveJobComposer-D1HUALME.js +19 -0
- package/client/dist/assets/JobDetailModal-CMg6aKSO.js +1 -0
- package/client/dist/assets/JobDetailPage-Bg01rzeu.js +1 -0
- package/client/dist/assets/JobsPage-Cwkm9yYc.js +1 -0
- package/client/dist/assets/{LoopBuilderPage-DsLLrtHa.js → LoopBuilderPage-4uPNN8Xn.js} +2 -2
- package/client/dist/assets/{LoopPreviewModal-C-fDEgnu.js → LoopPreviewModal-BNUQ9iL8.js} +1 -1
- package/client/dist/assets/LoopsPage-DLXESZxq.js +1 -0
- package/client/dist/assets/ProjectSettingsDialog-DP6hAug-.js +1 -0
- package/client/dist/assets/{TemplatePreviewModal-DcHFbLA8.js → TemplatePreviewModal-C6mndL8D.js} +1 -1
- package/client/dist/assets/{agent-Bwkyg2oX2.js → agent-B0Rg_eLw2.js} +1 -1
- package/client/dist/assets/{agent-DJ0EBUuP.js → agent-B8f79myL.js} +1 -1
- package/client/dist/assets/{agent-Bph_-xt7.js → agent-BCH7a3z-.js} +1 -1
- package/client/dist/assets/{agent-BI6vr-To.js → agent-BELAd4RF.js} +1 -1
- package/client/dist/assets/{agent-Cqs14QMr2.js → agent-BrCnHlvX2.js} +1 -1
- package/client/dist/assets/{agent-BUGXI3Ok2.js → agent-CrcIj6WE2.js} +1 -1
- package/client/dist/assets/{agent-CLCyDaZh.js → agent-CtwLUmjl.js} +1 -1
- package/client/dist/assets/{agent-Bvy2Csyd.js → agent-xG4qLg4R.js} +1 -1
- package/client/dist/assets/{brain-BBNUUrUI.js → brain-Dia32foa.js} +1 -1
- package/client/dist/assets/dashboard-5C2FOZQX.js +1 -0
- package/client/dist/assets/{dashboard-C6w0rICe.js → dashboard-B6iQ-IL_.js} +1 -1
- package/client/dist/assets/dashboard-BFfnCtq52.js +1 -0
- package/client/dist/assets/dashboard-BIC8KtRf2.js +1 -0
- package/client/dist/assets/dashboard-BawQCyrV.js +1 -0
- package/client/dist/assets/dashboard-CEgg9NAr.js +1 -0
- package/client/dist/assets/dashboard-CiOyDh9L.js +1 -0
- package/client/dist/assets/dashboard-D6x5OnNj2.js +1 -0
- package/client/dist/assets/{dist-js-DdP_Gl3p.js → dist-js-DN7Pxudf.js} +1 -1
- package/client/dist/assets/{dist-js-BxjTIvdT.js → dist-js-D_4k5prK.js} +1 -1
- package/client/dist/assets/index-BbVOi-dY.js +158 -0
- package/client/dist/assets/{index-C695aPdV.css → index-o3Q2Lii5.css} +1 -1
- package/client/dist/assets/{lib-BviHZ_gK.js → lib-BxSZPd0W.js} +1 -1
- package/client/dist/assets/settings-BFv6UbKV.js +1 -0
- package/client/dist/assets/{settings-BHdPchJP.js → settings-B_v3zgJf.js} +1 -1
- package/client/dist/assets/{settings-BeCczBFS2.js → settings-BemR_e3F.js} +1 -1
- package/client/dist/assets/{settings-D54_cgl12.js → settings-Bp24JHlv2.js} +1 -1
- package/client/dist/assets/{settings-C_nY14Yz.js → settings-BvIwQ3A1.js} +1 -1
- package/client/dist/assets/{settings-BlLi719f.js → settings-Bx2ObkTR2.js} +1 -1
- package/client/dist/assets/{settings-CaZ26a0S.js → settings-C_T0-j9d.js} +1 -1
- package/client/dist/assets/{settings-cn0tbve62.js → settings-DhaFKpcw2.js} +1 -1
- package/client/dist/assets/{upload-CRuuzzvp.js → upload-4u055Eea.js} +1 -1
- package/client/dist/assets/{value-CIr5hDMt.js → value-C3Qe3VXm.js} +1 -1
- package/client/dist/index.html +5 -5
- package/package.json +1 -1
- package/server/dist/agent-operator-prompt.js +10 -1
- package/server/dist/db.js +51 -2
- package/server/dist/dev-ports.js +35 -0
- package/server/dist/index.js +2 -6
- package/server/dist/mcp/tools/catalog.js +2 -0
- package/server/dist/mcp/tools/env.js +72 -0
- package/server/dist/mcp/tools/jobs.js +66 -1
- package/server/dist/path-resolver.js +74 -15
- package/server/dist/project-env.js +30 -0
- package/server/dist/project-registry.js +4 -2
- package/server/dist/project-router-jobs.js +76 -0
- package/server/dist/project-router-settings.js +10 -1
- package/server/dist/queue-manager.js +9 -1
- package/server/dist/rail-isolated-launch.js +5 -4
- package/server/dist/rail-pr-decision.js +27 -17
- package/server/dist/rail-pr-store.js +36 -0
- package/server/dist/rails-router.js +7 -1
- package/server/dist/transient-children.js +126 -8
- package/server/dist/workspace-resolution.js +3 -3
- package/server/dist/worktree-env-discovery.js +172 -0
- package/client/dist/assets/AgentBrowserCapture-DQyeEm2V.js +0 -1
- package/client/dist/assets/AgentModeJobsPane-C-g1WNVG.js +0 -1
- package/client/dist/assets/AgentsPage-BWgglK7V.js +0 -86
- package/client/dist/assets/CodePage-BUcAp7SC.js +0 -2
- package/client/dist/assets/DocsPage-D3fChfkj.js +0 -11
- package/client/dist/assets/IntegrationsPage-F87uxxVH.js +0 -3
- package/client/dist/assets/InteractiveJobComposer-DqDCiCa0.js +0 -19
- package/client/dist/assets/JobDetailModal-BWJR60fi.js +0 -1
- package/client/dist/assets/JobDetailPage-CjWE_jyd.js +0 -1
- package/client/dist/assets/JobsPage-LmnXkyCa.js +0 -1
- package/client/dist/assets/LoopsPage-DDNO9h9T.js +0 -1
- package/client/dist/assets/ProjectSettingsDialog-CX854C8e.js +0 -1
- package/client/dist/assets/dashboard-BjjS1OrL.js +0 -1
- package/client/dist/assets/dashboard-Bk6oSk1H2.js +0 -1
- package/client/dist/assets/dashboard-BrH-GdOg2.js +0 -1
- package/client/dist/assets/dashboard-BurhUI8_.js +0 -1
- package/client/dist/assets/dashboard-CAUnfDQL2.js +0 -1
- package/client/dist/assets/dashboard-C_ljpeYO.js +0 -1
- package/client/dist/assets/dashboard-D6OqDdj6.js +0 -1
- package/client/dist/assets/index-Dw1N2-ci.js +0 -156
- package/client/dist/assets/settings-D9bDDe2O.js +0 -1
- /package/client/dist/assets/{activity-BZNfOdiS.js → activity-BZZlEG3a.js} +0 -0
- /package/client/dist/assets/{activity--iN4Ad2R2.js → activity-BbWiDfn-2.js} +0 -0
- /package/client/dist/assets/{activity-CIOLxGvh.js → activity-D3Ic-HTS.js} +0 -0
- /package/client/dist/assets/{activity-DtxrAUMo2.js → activity-DFbvNqhE2.js} +0 -0
- /package/client/dist/assets/{activity-BuVo5sPB.js → activity-DtzRy92x.js} +0 -0
- /package/client/dist/assets/{activity-6hM_ZSg9.js → activity-DvECTYGt.js} +0 -0
- /package/client/dist/assets/{activity-D2r2h3SE2.js → activity-FleOlK0l2.js} +0 -0
- /package/client/dist/assets/{addspec-D-JLDsUV.js → addspec-BKrH2Ucz.js} +0 -0
- /package/client/dist/assets/{addspec-DfhDbg5c.js → addspec-B_LQMHW3.js} +0 -0
- /package/client/dist/assets/{addspec-bsNDdqRm2.js → addspec-Bw_YQwo62.js} +0 -0
- /package/client/dist/assets/{addspec-BMtLZLW62.js → addspec-CFxGvmJR2.js} +0 -0
- /package/client/dist/assets/{addspec-mgPOt5q02.js → addspec-CR8MB5Fr2.js} +0 -0
- /package/client/dist/assets/{addspec-CP0HRuxY.js → addspec-DaA-CbTt.js} +0 -0
- /package/client/dist/assets/{addspec-CRuvWn8d.js → addspec-NBbcTbLc.js} +0 -0
- /package/client/dist/assets/{agents-BpoUaoW92.js → agents-B9pFSJQI2.js} +0 -0
- /package/client/dist/assets/{agents-78bTZGZh.js → agents-BHgn--3j.js} +0 -0
- /package/client/dist/assets/{agents-BUdEi3-N.js → agents-B_qGaB0F.js} +0 -0
- /package/client/dist/assets/{agents-bvyRLczL.js → agents-CP8_ezYG.js} +0 -0
- /package/client/dist/assets/{agents-BLhWCmDS2.js → agents-CV-_xBvX2.js} +0 -0
- /package/client/dist/assets/{agents-CWo-KyAB.js → agents-HFCqQH0Z.js} +0 -0
- /package/client/dist/assets/{agents-DDtCJ5M02.js → agents-VsyFhGOS2.js} +0 -0
- /package/client/dist/assets/{agentstudio-BMQxc_bz.js → agentstudio-BEp7Sb46.js} +0 -0
- /package/client/dist/assets/{agentstudio-Dtq3dGYk.js → agentstudio-BOjG6tgJ.js} +0 -0
- /package/client/dist/assets/{agentstudio-C_Jq8Tl4.js → agentstudio-Br3WD7Xs.js} +0 -0
- /package/client/dist/assets/{agentstudio-sI27TpBu2.js → agentstudio-CuTt3ZGe2.js} +0 -0
- /package/client/dist/assets/{agentstudio-CWgXG5W52.js → agentstudio-D2A5peWV2.js} +0 -0
- /package/client/dist/assets/{agentstudio-COZriimI2.js → agentstudio-RfZavULt2.js} +0 -0
- /package/client/dist/assets/{agentstudio-jKMTdT70.js → agentstudio-RsxnZtUA.js} +0 -0
- /package/client/dist/assets/{aiedit-igmw-ZoZ2.js → aiedit-26p5A_HY2.js} +0 -0
- /package/client/dist/assets/{aiedit-6CHdb_iv2.js → aiedit-CV520D4W2.js} +0 -0
- /package/client/dist/assets/{aiedit-pc5JDUq1.js → aiedit-CcugGPbS.js} +0 -0
- /package/client/dist/assets/{aiedit-eMN4eQPF.js → aiedit-D0Syl-mC.js} +0 -0
- /package/client/dist/assets/{aiedit-hknZlly82.js → aiedit-DnBzjlVz2.js} +0 -0
- /package/client/dist/assets/{aiedit-B03pD_Na.js → aiedit-DrIjSojo.js} +0 -0
- /package/client/dist/assets/{aiedit-BtRqRxML.js → aiedit-a-Z-g5iC.js} +0 -0
- /package/client/dist/assets/{analytics-C5dpiS0o.js → analytics-4-eskbtX.js} +0 -0
- /package/client/dist/assets/{analytics-CR1TJqUi2.js → analytics-4HvXb0Vn2.js} +0 -0
- /package/client/dist/assets/{analytics-rmHuJv0V.js → analytics-Bd9rHGjV.js} +0 -0
- /package/client/dist/assets/{analytics-BWMUZ2Pq2.js → analytics-Bpe41fnX2.js} +0 -0
- /package/client/dist/assets/{analytics-Bj-SmKHa.js → analytics-CpSTNfui.js} +0 -0
- /package/client/dist/assets/{analytics-CCprAqk3.js → analytics-DEe5BMz8.js} +0 -0
- /package/client/dist/assets/{analytics-DfNdXR0y2.js → analytics-DcOITHla2.js} +0 -0
- /package/client/dist/assets/{attachments-BjyMmrwE.js → attachments-CA2f3qL3.js} +0 -0
- /package/client/dist/assets/{attachments-DCZOL1RU.js → attachments-CHDbhKeH.js} +0 -0
- /package/client/dist/assets/{attachments-rNn1ekRy2.js → attachments-CKAY5aYG2.js} +0 -0
- /package/client/dist/assets/{attachments-BgBNSqQd.js → attachments-CgOQ9K-6.js} +0 -0
- /package/client/dist/assets/{attachments-DPCmkFZu2.js → attachments-D2jwJXQ42.js} +0 -0
- /package/client/dist/assets/{attachments-C9GTTJsx2.js → attachments-DlEQu56g2.js} +0 -0
- /package/client/dist/assets/{attachments-DL-bPy9E.js → attachments-jVdKzAXn.js} +0 -0
- /package/client/dist/assets/{browser-Cvg-QHvd2.js → browser-BQFDxrRq.js} +0 -0
- /package/client/dist/assets/{browser-rWKj-RR_2.js → browser-BbJ_q_OT2.js} +0 -0
- /package/client/dist/assets/{browser-Cc0iDqUi.js → browser-C4VJ5CW12.js} +0 -0
- /package/client/dist/assets/{browser-BPkhILSJ.js → browser-Cl-FwPXs.js} +0 -0
- /package/client/dist/assets/{browser-FRv8P02E.js → browser-Do2jf8_N.js} +0 -0
- /package/client/dist/assets/{browser-BqLqJVVT.js → browser-JsiohNmW.js} +0 -0
- /package/client/dist/assets/{browser-BijVYG0Y2.js → browser-kA31eYhu2.js} +0 -0
- /package/client/dist/assets/{chat-DdwZjT8K2.js → chat-BMWD_Rx12.js} +0 -0
- /package/client/dist/assets/{chat-Bom6bqK3.js → chat-BalviUAD.js} +0 -0
- /package/client/dist/assets/{chat-DsbwJhus.js → chat-CJddjN3D2.js} +0 -0
- /package/client/dist/assets/{chat-Dnwg-NpQ.js → chat-D5xEtsf3.js} +0 -0
- /package/client/dist/assets/{chat-vylxqR0P2.js → chat-Ds8GvCnv.js} +0 -0
- /package/client/dist/assets/{chat-BgL3zv6x2.js → chat-SaG-ng1x2.js} +0 -0
- /package/client/dist/assets/{chat-Bh3NdgJs.js → chat-n43nZGvn.js} +0 -0
- /package/client/dist/assets/{code-DzC1rMk12.js → code-Bit_z2Xd2.js} +0 -0
- /package/client/dist/assets/{code-BRcDlze52.js → code-BotxM43N2.js} +0 -0
- /package/client/dist/assets/{code-UPVvmAKM2.js → code-C9U6M5-e2.js} +0 -0
- /package/client/dist/assets/{code-BdMy3Ffq.js → code-CIPmDEd0.js} +0 -0
- /package/client/dist/assets/{code-DgeHz9CB.js → code-DUe1Bouz.js} +0 -0
- /package/client/dist/assets/{code-CwnBPPyA.js → code-F_KjccGf.js} +0 -0
- /package/client/dist/assets/{code-CeQ0FZpC.js → code-HDK4P6Rx.js} +0 -0
- /package/client/dist/assets/{commands-DkU0qliq.js → commands-BFQZc9AG.js} +0 -0
- /package/client/dist/assets/{commands-u-4eEHCN.js → commands-BMkC5-ki.js} +0 -0
- /package/client/dist/assets/{commands-CSDs9q_2.js → commands-BllkJauO.js} +0 -0
- /package/client/dist/assets/{commands-xHDDGfcP2.js → commands-CWX9WmDX2.js} +0 -0
- /package/client/dist/assets/{commands-C4Y3C-PF2.js → commands-CzFSoIiq2.js} +0 -0
- /package/client/dist/assets/{commands-JWMfGDmr.js → commands-KBbhsnw8.js} +0 -0
- /package/client/dist/assets/{commands-RHOchN4n2.js → commands-cKTaLwwV2.js} +0 -0
- /package/client/dist/assets/{common-DmqBPwZG.js → common-BfQD173j.js} +0 -0
- /package/client/dist/assets/{common-DhWTkocg.js → common-CF8iGXrG.js} +0 -0
- /package/client/dist/assets/{common-Bwafujnc2.js → common-Cfmwcrl52.js} +0 -0
- /package/client/dist/assets/{common-C-vU-j-I.js → common-D2cimsul.js} +0 -0
- /package/client/dist/assets/{common-C5HKSbbz.js → common-DH7fjyL1.js} +0 -0
- /package/client/dist/assets/{common-BM3LFLCE2.js → common-DZbXlmq72.js} +0 -0
- /package/client/dist/assets/{common-DiPTT7V_2.js → common-DtXmzX2o2.js} +0 -0
- /package/client/dist/assets/{explore-UvW1jEMa.js → explore-BEpIs343.js} +0 -0
- /package/client/dist/assets/{explore-CNBzrEPS.js → explore-BO_OinBD.js} +0 -0
- /package/client/dist/assets/{explore-C0k8HrkN.js → explore-Bhy7WWXv.js} +0 -0
- /package/client/dist/assets/{explore-B7fblXBb2.js → explore-CAc8Qzu32.js} +0 -0
- /package/client/dist/assets/{explore-D_mWBYCS2.js → explore-DCZL9gRN2.js} +0 -0
- /package/client/dist/assets/{explore-Dahc1lOx.js → explore-DW_YTkQh.js} +0 -0
- /package/client/dist/assets/{explore-BHtEpoNN2.js → explore-ELbrSZyQ2.js} +0 -0
- /package/client/dist/assets/{format-command-CuWuRvka.js → format-command-Db1viey1.js} +0 -0
- /package/client/dist/assets/{integrations-BNuauK3o.js → integrations-BQNkrfr7.js} +0 -0
- /package/client/dist/assets/{integrations-Cl_wD5AI.js → integrations-BdN2f6bP.js} +0 -0
- /package/client/dist/assets/{integrations-CHy8tnpV.js → integrations-BrIUJmVf.js} +0 -0
- /package/client/dist/assets/{integrations-Cy8nZ_dv2.js → integrations-Cyp7cSYv2.js} +0 -0
- /package/client/dist/assets/{integrations-4KZnYECp.js → integrations-DDR3OHdq.js} +0 -0
- /package/client/dist/assets/{integrations-7W266oqF2.js → integrations-DVxi8b0j2.js} +0 -0
- /package/client/dist/assets/{integrations-zMqY4VU12.js → integrations-RuNEX3La2.js} +0 -0
- /package/client/dist/assets/{jira-uNH1gETL2.js → jira-B5wszJ1-2.js} +0 -0
- /package/client/dist/assets/{jira-BHAKtc4B.js → jira-C5UjxHE4.js} +0 -0
- /package/client/dist/assets/{jira-D5efgwp_.js → jira-CQ7mrZde.js} +0 -0
- /package/client/dist/assets/{jira-bDs7Im2C.js → jira-DIRkZk9U.js} +0 -0
- /package/client/dist/assets/{jira-CPpgA-5z2.js → jira-DTYu3hJi2.js} +0 -0
- /package/client/dist/assets/{jira-DS7ownDy.js → jira-DtZpokjJ.js} +0 -0
- /package/client/dist/assets/{jira-Be369RIR2.js → jira-ZdKb-50F2.js} +0 -0
- /package/client/dist/assets/{jobs-CE8qeIYj.js → jobs-BFIdeDTw.js} +0 -0
- /package/client/dist/assets/{jobs-BgQzB0Sx2.js → jobs-BJ-Gcao22.js} +0 -0
- /package/client/dist/assets/{jobs-1GV2-4zd2.js → jobs-DAGiPVIS2.js} +0 -0
- /package/client/dist/assets/{jobs-26F9gGcE.js → jobs-DAU8C_tZ.js} +0 -0
- /package/client/dist/assets/{jobs-BqWui9_f.js → jobs-Da0GaaSc.js} +0 -0
- /package/client/dist/assets/{jobs-Wh7dn28q2.js → jobs-P3unxtxo2.js} +0 -0
- /package/client/dist/assets/{jobs-DzeuASRf.js → jobs-p360zdf1.js} +0 -0
- /package/client/dist/assets/{loops-CvOWsi5r.js → loops-Bi4ywYGn.js} +0 -0
- /package/client/dist/assets/{loops-BvqQ-bHR2.js → loops-BjK8lmoZ2.js} +0 -0
- /package/client/dist/assets/{loops-K5_HXfJa.js → loops-D3wdUkZV.js} +0 -0
- /package/client/dist/assets/{loops-D6D3XHjj.js → loops-DDs4KPu_.js} +0 -0
- /package/client/dist/assets/{loops-LIgO_-eM.js → loops-DKzYFcq6.js} +0 -0
- /package/client/dist/assets/{loops-DJNhp17p2.js → loops-Du-bNVpc2.js} +0 -0
- /package/client/dist/assets/{loops-vhJdbcz92.js → loops-DvGbSfxw2.js} +0 -0
- /package/client/dist/assets/{mcp-XJNRfxVp2.js → mcp-BG-OCJca2.js} +0 -0
- /package/client/dist/assets/{mcp-DtbxuH3V2.js → mcp-CbJ04oFS2.js} +0 -0
- /package/client/dist/assets/{mcp-sRmWE8WJ.js → mcp-CrWdq5db.js} +0 -0
- /package/client/dist/assets/{mcp-H-gnQUdN.js → mcp-Dd844F5_.js} +0 -0
- /package/client/dist/assets/{mcp-Dcl-XxkR.js → mcp-DfmzoT5t.js} +0 -0
- /package/client/dist/assets/{mcp-CjVKXAhD2.js → mcp-DyULurYS2.js} +0 -0
- /package/client/dist/assets/{mcp-N1nzp8oL.js → mcp-zSJ2TsJj.js} +0 -0
- /package/client/dist/assets/{nav-EI_OhEAo.js → nav-BFCUtHUQ2.js} +0 -0
- /package/client/dist/assets/{nav-CGy4yx3C.js → nav-BouzorQ5.js} +0 -0
- /package/client/dist/assets/{nav-40HzpIFb.js → nav-Bpr-K0tD.js} +0 -0
- /package/client/dist/assets/{nav-B6PkGfTk2.js → nav-CpFrRDXd2.js} +0 -0
- /package/client/dist/assets/{nav-CXEDcRdv2.js → nav-Dkv4Vlr_2.js} +0 -0
- /package/client/dist/assets/{nav-8yPhBn00.js → nav-GG6azCbh.js} +0 -0
- /package/client/dist/assets/{nav-BHqMLrS72.js → nav-prHkMqcr.js} +0 -0
- /package/client/dist/assets/{setup-BuQNKztt2.js → setup-1vN48B-m2.js} +0 -0
- /package/client/dist/assets/{setup-BwMovM9z2.js → setup-BmJ0Kx16.js} +0 -0
- /package/client/dist/assets/{setup-BXpw07r-.js → setup-DQWsu2TR.js} +0 -0
- /package/client/dist/assets/{setup-D84y3wx-.js → setup-JeBwoXoM.js} +0 -0
- /package/client/dist/assets/{setup-DU0ULX8q2.js → setup-T_ApeQDV2.js} +0 -0
- /package/client/dist/assets/{setup-BiduSX9d.js → setup-mXnzww4f.js} +0 -0
- /package/client/dist/assets/{setup-7J8TjG4O.js → setup-q0wRHUp1.js} +0 -0
- /package/client/dist/assets/{specs-CWtKcVDC.js → specs-BGRKhM8t.js} +0 -0
- /package/client/dist/assets/{specs-CBDvEYKv.js → specs-C6J2zrLM.js} +0 -0
- /package/client/dist/assets/{specs-D2clrt2c2.js → specs-CT8exGOy2.js} +0 -0
- /package/client/dist/assets/{specs-C9Sd7BhH2.js → specs-Ce5y2iFc2.js} +0 -0
- /package/client/dist/assets/{specs-CIPr9reA2.js → specs-DZDoZsDq.js} +0 -0
- /package/client/dist/assets/{specs-PozUNhqb.js → specs-DbtIzV8a.js} +0 -0
- /package/client/dist/assets/{specs-DTplt2XM.js → specs-GkiL-snY.js} +0 -0
- /package/client/dist/assets/{terminal-xGbCZJHg.js → terminal-BNSg2lR7.js} +0 -0
- /package/client/dist/assets/{terminal-De2Brd23.js → terminal-BTpE7iki.js} +0 -0
- /package/client/dist/assets/{terminal-BRm03iFA2.js → terminal-Cvabyzm42.js} +0 -0
- /package/client/dist/assets/{terminal-DxQWCqSD2.js → terminal-D0z12_sJ2.js} +0 -0
- /package/client/dist/assets/{terminal-Z7KsDM_n.js → terminal-DDk_bTw0.js} +0 -0
- /package/client/dist/assets/{terminal-cqfd3EDV.js → terminal-DEyt0prF.js} +0 -0
- /package/client/dist/assets/{terminal-WSAvHXoU.js → terminal-a6Uq8B0p.js} +0 -0
- /package/client/dist/assets/{tickets-C3b8cHhd2.js → tickets-BIxU86j12.js} +0 -0
- /package/client/dist/assets/{tickets-SuX_HkK4.js → tickets-C2BhwzwR.js} +0 -0
- /package/client/dist/assets/{tickets-DTHo9c3C.js → tickets-D1IeX2EW.js} +0 -0
- /package/client/dist/assets/{tickets-BoXom7l8.js → tickets-DZut8Ryt.js} +0 -0
- /package/client/dist/assets/{tickets-CWYFvx1W2.js → tickets-LfK6LzKl2.js} +0 -0
- /package/client/dist/assets/{tickets-CFXnJsYF.js → tickets-dQfsc7Ph.js} +0 -0
- /package/client/dist/assets/{tickets-B7zT4IRf.js → tickets-sVun0I6v.js} +0 -0
|
@@ -1,8 +1,42 @@
|
|
|
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
|
+
function resolveBackgroundCwd(projectRoot, cwd) {
|
|
12
|
+
const resolved = path_1.default.resolve(projectRoot, cwd && cwd.trim() ? cwd : '.');
|
|
13
|
+
const root = path_1.default.resolve(projectRoot);
|
|
14
|
+
if (resolved !== root && !resolved.startsWith(root + path_1.default.sep)) {
|
|
15
|
+
throw new Error('background_start cwd must stay within the selected project.');
|
|
16
|
+
}
|
|
17
|
+
return resolved;
|
|
18
|
+
}
|
|
19
|
+
function backgroundHooks(broadcast) {
|
|
20
|
+
return {
|
|
21
|
+
onStarted: (process) => broadcast({
|
|
22
|
+
type: 'background_process.started',
|
|
23
|
+
process,
|
|
24
|
+
projectId: process.projectId,
|
|
25
|
+
timestamp: new Date().toISOString(),
|
|
26
|
+
}),
|
|
27
|
+
onOutput: (event) => broadcast({
|
|
28
|
+
type: 'background_process.output',
|
|
29
|
+
...event,
|
|
30
|
+
timestamp: new Date().toISOString(),
|
|
31
|
+
}),
|
|
32
|
+
onExited: (process) => broadcast({
|
|
33
|
+
type: 'background_process.exited',
|
|
34
|
+
process,
|
|
35
|
+
projectId: process.projectId,
|
|
36
|
+
timestamp: new Date().toISOString(),
|
|
37
|
+
}),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
6
40
|
/**
|
|
7
41
|
* Jobs domain facade. Maps every in-scope jobs/queue action to its REST
|
|
8
42
|
* endpoint on the loopback API (server/project-router-jobs.ts, plus the
|
|
@@ -20,6 +54,7 @@ function jobsTools() {
|
|
|
20
54
|
title: 'Jobs & Queue',
|
|
21
55
|
description: 'Manage a project\'s AI-pipeline job queue and individual jobs. ' +
|
|
22
56
|
'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), ' +
|
|
57
|
+
'background_start (operate-level shell command tied to the current agent chat; requires explicit user confirmation before use), background_kill, ' +
|
|
23
58
|
'cancel (destructive — cancels a running/queued job or deletes a terminal one), ' +
|
|
24
59
|
'purge (destructive — bulk-delete persisted job rows in a date range), ' +
|
|
25
60
|
'pause / resume (queue), reorder (queued-job order), priority (change a queued job\'s priority), ' +
|
|
@@ -34,7 +69,7 @@ function jobsTools() {
|
|
|
34
69
|
const action = a.action;
|
|
35
70
|
if (['cancel', 'purge'].includes(action))
|
|
36
71
|
return 'destructive';
|
|
37
|
-
if (['spawn', 'interactive_turn'].includes(action))
|
|
72
|
+
if (['spawn', 'interactive_turn', 'background_start', 'background_kill'].includes(action))
|
|
38
73
|
return 'ai-spawn';
|
|
39
74
|
if (['pause', 'resume', 'reorder', 'priority', 'finalize'].includes(action))
|
|
40
75
|
return 'write';
|
|
@@ -64,6 +99,8 @@ function jobsTools() {
|
|
|
64
99
|
'default_spec_model',
|
|
65
100
|
'interactive_turn',
|
|
66
101
|
'finalize',
|
|
102
|
+
'background_start',
|
|
103
|
+
'background_kill',
|
|
67
104
|
])
|
|
68
105
|
.describe('Operation to perform'),
|
|
69
106
|
projectId: zod_1.z.string().optional().describe('Project id (defaults to the active project)'),
|
|
@@ -99,6 +136,9 @@ function jobsTools() {
|
|
|
99
136
|
.describe('reorder: exact set of currently-queued job ids in the desired order; compare: exactly 2 job ids'),
|
|
100
137
|
// ── interactive_turn ──
|
|
101
138
|
text: zod_1.z.string().optional().describe('Prompt text for interactive_turn'),
|
|
139
|
+
chatId: zod_1.z.string().optional().describe('Agent chat conversation id for background_start/background_kill ownership; defaults to the in-app agent conversation that called the tool'),
|
|
140
|
+
cwd: zod_1.z.string().optional().describe('Optional cwd for background_start; resolved inside the selected project root'),
|
|
141
|
+
pid: zod_1.z.number().optional().describe('Background process pid for background_kill'),
|
|
102
142
|
// ── export ──
|
|
103
143
|
format: zod_1.z.enum(['json', 'csv']).optional().describe('Export format (default json)'),
|
|
104
144
|
// ── default_spec_model ──
|
|
@@ -270,6 +310,31 @@ function jobsTools() {
|
|
|
270
310
|
throw new Error('finalize requires a "jobId".');
|
|
271
311
|
return (0, types_1.apiCall)(ctx, 'POST', `${base}/jobs/${encodeURIComponent(id)}/finalize`);
|
|
272
312
|
}
|
|
313
|
+
case 'background_start': {
|
|
314
|
+
const command = args.command;
|
|
315
|
+
if (!command || !command.trim())
|
|
316
|
+
throw new Error('background_start requires a "command".');
|
|
317
|
+
const chatId = args.chatId ?? ctx.originConversationId ?? undefined;
|
|
318
|
+
if (!chatId || !chatId.trim())
|
|
319
|
+
throw new Error('background_start requires a "chatId".');
|
|
320
|
+
const projectCtx = (0, types_1.requireProject)(ctx, args.projectId);
|
|
321
|
+
const cwd = resolveBackgroundCwd(projectCtx.project.path, args.cwd);
|
|
322
|
+
const process = (0, transient_children_1.startBackgroundProcess)(command.trim(), cwd, chatId.trim(), projectCtx.project.id, backgroundHooks(ctx.broadcast));
|
|
323
|
+
return { ok: true, process };
|
|
324
|
+
}
|
|
325
|
+
case 'background_kill': {
|
|
326
|
+
const pid = args.pid;
|
|
327
|
+
if (typeof pid !== 'number' || !Number.isFinite(pid))
|
|
328
|
+
throw new Error('background_kill requires a numeric "pid".');
|
|
329
|
+
const chatId = args.chatId ?? ctx.originConversationId ?? undefined;
|
|
330
|
+
if (!chatId || !chatId.trim())
|
|
331
|
+
throw new Error('background_kill requires a "chatId".');
|
|
332
|
+
const projectCtx = (0, types_1.requireProject)(ctx, args.projectId);
|
|
333
|
+
const killed = (0, transient_children_1.killOwnedBackgroundProcess)(pid, { projectId: projectCtx.project.id, chatId: chatId.trim() });
|
|
334
|
+
if (!killed)
|
|
335
|
+
throw new Error('background_kill requires a registered pid in the same project/chat.');
|
|
336
|
+
return { ok: true, pid, status: 'killing' };
|
|
337
|
+
}
|
|
273
338
|
default:
|
|
274
339
|
throw new Error(`Unknown action "${action}".`);
|
|
275
340
|
}
|
|
@@ -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;
|
|
@@ -464,6 +466,24 @@ exports.AUTH_ENV_VARS = [
|
|
|
464
466
|
'GOOGLE_CLOUD_LOCATION',
|
|
465
467
|
'GOOGLE_APPLICATION_CREDENTIALS',
|
|
466
468
|
];
|
|
469
|
+
const LOGIN_SHELL_ENV_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
470
|
+
const loginShellEnvProbeCache = new Set();
|
|
471
|
+
function normalizeLoginShellEnvNames(names) {
|
|
472
|
+
const out = [];
|
|
473
|
+
for (const raw of names) {
|
|
474
|
+
const name = String(raw).trim();
|
|
475
|
+
if (!LOGIN_SHELL_ENV_NAME_RE.test(name) || out.includes(name))
|
|
476
|
+
continue;
|
|
477
|
+
out.push(name);
|
|
478
|
+
}
|
|
479
|
+
return out;
|
|
480
|
+
}
|
|
481
|
+
function buildLoginShellEnvCommand(names) {
|
|
482
|
+
// `printf` reuses POSIX positional `$VAR` expansion (works in sh/bash/zsh).
|
|
483
|
+
const fmt = ENV_BEGIN + names.map((v) => `${v}=%s\n`).join('') + ENV_END;
|
|
484
|
+
const refs = names.map((v) => `"$${v}"`).join(' ');
|
|
485
|
+
return `printf '${fmt}' ${refs}`;
|
|
486
|
+
}
|
|
467
487
|
/** Parse the `KEY=value` block emitted between the env sentinels. */
|
|
468
488
|
function parseLoginShellEnv(stdout) {
|
|
469
489
|
const begin = stdout.indexOf(ENV_BEGIN);
|
|
@@ -486,27 +506,23 @@ function parseLoginShellEnv(stdout) {
|
|
|
486
506
|
}
|
|
487
507
|
return out;
|
|
488
508
|
}
|
|
489
|
-
/**
|
|
490
|
-
*
|
|
491
|
-
*
|
|
492
|
-
*
|
|
493
|
-
*
|
|
494
|
-
|
|
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 = {}) {
|
|
509
|
+
/** Backfill arbitrary, pre-validated environment variable names from the user's
|
|
510
|
+
* login shell into `process.env` (only when not already set — never override an
|
|
511
|
+
* explicit value). POSIX-only and runs regardless of bundle state, so macOS GUI
|
|
512
|
+
* launches can recover tokens exported in `.zshrc`/`.bashrc` without hardcoding
|
|
513
|
+
* every possible package-manager/provider variable. No-op on Windows and tests. */
|
|
514
|
+
async function augmentEnvFromLoginShell(names, opts = {}) {
|
|
499
515
|
if (process.platform === 'win32')
|
|
500
516
|
return;
|
|
501
517
|
if (process.env.NODE_ENV === 'test' || process.env.VITEST === 'true')
|
|
502
518
|
return;
|
|
519
|
+
const wanted = normalizeLoginShellEnvNames(names).filter((name) => !process.env[name]);
|
|
520
|
+
if (wanted.length === 0)
|
|
521
|
+
return;
|
|
503
522
|
const spawnFn = opts.spawnFn ?? child_process_1.spawn;
|
|
504
523
|
const timeoutMs = opts.timeoutMs ?? LOGIN_SHELL_TIMEOUT_MS;
|
|
505
524
|
const shell = process.env.SHELL || '/bin/sh';
|
|
506
|
-
|
|
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}`;
|
|
525
|
+
const command = buildLoginShellEnvCommand(wanted);
|
|
510
526
|
await new Promise((resolve) => {
|
|
511
527
|
let child;
|
|
512
528
|
try {
|
|
@@ -536,7 +552,7 @@ async function augmentAuthEnvFromLoginShell(opts = {}) {
|
|
|
536
552
|
child.on('error', finish);
|
|
537
553
|
child.on('close', () => {
|
|
538
554
|
const recovered = parseLoginShellEnv(stdout);
|
|
539
|
-
for (const key of
|
|
555
|
+
for (const key of wanted) {
|
|
540
556
|
const val = recovered[key];
|
|
541
557
|
// Backfill only when unset/empty — never clobber an explicit value.
|
|
542
558
|
if (val && !process.env[key])
|
|
@@ -546,6 +562,48 @@ async function augmentAuthEnvFromLoginShell(opts = {}) {
|
|
|
546
562
|
});
|
|
547
563
|
});
|
|
548
564
|
}
|
|
565
|
+
/** Synchronous, cached variant for spawn-time env assembly. It probes each
|
|
566
|
+
* missing name at most once per process to keep loops from paying the login
|
|
567
|
+
* shell timeout on every step. */
|
|
568
|
+
function augmentEnvFromLoginShellSync(names, opts = {}) {
|
|
569
|
+
if (process.platform === 'win32')
|
|
570
|
+
return;
|
|
571
|
+
if (process.env.NODE_ENV === 'test' || process.env.VITEST === 'true')
|
|
572
|
+
return;
|
|
573
|
+
const wanted = normalizeLoginShellEnvNames(names).filter((name) => !process.env[name] && !loginShellEnvProbeCache.has(name));
|
|
574
|
+
if (wanted.length === 0)
|
|
575
|
+
return;
|
|
576
|
+
for (const name of wanted)
|
|
577
|
+
loginShellEnvProbeCache.add(name);
|
|
578
|
+
const spawnSyncFn = opts.spawnSyncFn ?? child_process_1.spawnSync;
|
|
579
|
+
const timeoutMs = opts.timeoutMs ?? LOGIN_SHELL_TIMEOUT_MS;
|
|
580
|
+
const shell = process.env.SHELL || '/bin/sh';
|
|
581
|
+
const command = buildLoginShellEnvCommand(wanted);
|
|
582
|
+
let res;
|
|
583
|
+
try {
|
|
584
|
+
res = spawnSyncFn(shell, ['-l', '-i', '-c', command], {
|
|
585
|
+
encoding: 'utf8',
|
|
586
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
587
|
+
timeout: timeoutMs,
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
catch {
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
if (res.error)
|
|
594
|
+
return;
|
|
595
|
+
const stdout = typeof res.stdout === 'string' ? res.stdout : res.stdout?.toString('utf8') ?? '';
|
|
596
|
+
const recovered = parseLoginShellEnv(stdout);
|
|
597
|
+
for (const key of wanted) {
|
|
598
|
+
const val = recovered[key];
|
|
599
|
+
if (val && !process.env[key])
|
|
600
|
+
process.env[key] = val;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
/** Startup backfill for the built-in provider auth variables. */
|
|
604
|
+
async function augmentAuthEnvFromLoginShell(opts = {}) {
|
|
605
|
+
await augmentEnvFromLoginShell(exports.AUTH_ENV_VARS, opts);
|
|
606
|
+
}
|
|
549
607
|
function mergeLoginShellPath(rawPath) {
|
|
550
608
|
const current = splitPath(process.env.PATH);
|
|
551
609
|
const currentSet = new Set(current);
|
|
@@ -582,4 +640,5 @@ function __resetPathResolverForTest() {
|
|
|
582
640
|
diagnostic = { pathSegments: [], pathSources: [], loginShellStatus: 'skipped' };
|
|
583
641
|
warnedLoginShell = false;
|
|
584
642
|
bundledRuntimesActive = false;
|
|
643
|
+
loginShellEnvProbeCache.clear();
|
|
585
644
|
}
|
|
@@ -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
|
|
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 {
|
|
@@ -1,6 +1,10 @@
|
|
|
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.registerJobsRoutes = registerJobsRoutes;
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
4
8
|
const db_1 = require("./db");
|
|
5
9
|
const queue_manager_1 = require("./queue-manager");
|
|
6
10
|
const feature_flags_1 = require("./feature-flags");
|
|
@@ -12,6 +16,7 @@ const provider_selection_1 = require("./provider-selection");
|
|
|
12
16
|
const loop_runs_store_1 = require("./loop-runs-store");
|
|
13
17
|
const metrics_1 = require("./metrics");
|
|
14
18
|
const ticket_store_1 = require("./ticket-store");
|
|
19
|
+
const transient_children_1 = require("./transient-children");
|
|
15
20
|
const project_router_helpers_1 = require("./project-router-helpers");
|
|
16
21
|
function registerJobsRoutes(deps) {
|
|
17
22
|
const { router, registry, ctx, ticketPath } = deps;
|
|
@@ -86,6 +91,77 @@ function registerJobsRoutes(deps) {
|
|
|
86
91
|
},
|
|
87
92
|
});
|
|
88
93
|
});
|
|
94
|
+
router.get('/:projectId/background-processes', (req, res) => {
|
|
95
|
+
const c = ctx(req);
|
|
96
|
+
const chatId = typeof req.query.chatId === 'string' && req.query.chatId.trim() ? req.query.chatId.trim() : undefined;
|
|
97
|
+
res.json({
|
|
98
|
+
processes: (0, transient_children_1.listBackgroundProcesses)({
|
|
99
|
+
projectId: c.project.id,
|
|
100
|
+
...(chatId ? { chatId } : {}),
|
|
101
|
+
}),
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
router.post('/:projectId/background-processes', (req, res) => {
|
|
105
|
+
const { command, cwd, chatId } = req.body ?? {};
|
|
106
|
+
const c = ctx(req);
|
|
107
|
+
if (!command || typeof command !== 'string' || !command.trim()) {
|
|
108
|
+
res.status(400).json({ error: 'command is required' });
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (!chatId || typeof chatId !== 'string' || !chatId.trim()) {
|
|
112
|
+
res.status(400).json({ error: 'chatId is required' });
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const resolvedCwd = path_1.default.resolve(c.project.path, typeof cwd === 'string' && cwd.trim() ? cwd : '.');
|
|
116
|
+
const root = path_1.default.resolve(c.project.path);
|
|
117
|
+
if (resolvedCwd !== root && !resolvedCwd.startsWith(root + path_1.default.sep)) {
|
|
118
|
+
res.status(400).json({ error: 'cwd must stay within the project' });
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
try {
|
|
122
|
+
const process = (0, transient_children_1.startBackgroundProcess)(command.trim(), resolvedCwd, chatId.trim(), c.project.id, {
|
|
123
|
+
onStarted: (process) => c.broadcast({
|
|
124
|
+
type: 'background_process.started',
|
|
125
|
+
process,
|
|
126
|
+
projectId: process.projectId,
|
|
127
|
+
timestamp: new Date().toISOString(),
|
|
128
|
+
}),
|
|
129
|
+
onOutput: (event) => c.broadcast({
|
|
130
|
+
type: 'background_process.output',
|
|
131
|
+
...event,
|
|
132
|
+
timestamp: new Date().toISOString(),
|
|
133
|
+
}),
|
|
134
|
+
onExited: (process) => c.broadcast({
|
|
135
|
+
type: 'background_process.exited',
|
|
136
|
+
process,
|
|
137
|
+
projectId: process.projectId,
|
|
138
|
+
timestamp: new Date().toISOString(),
|
|
139
|
+
}),
|
|
140
|
+
});
|
|
141
|
+
res.status(202).json({ ok: true, process });
|
|
142
|
+
}
|
|
143
|
+
catch (err) {
|
|
144
|
+
res.status(500).json({ error: err instanceof Error ? err.message : 'background process failed to start' });
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
router.delete('/:projectId/background-processes/:pid', (req, res) => {
|
|
148
|
+
const c = ctx(req);
|
|
149
|
+
const pid = Number(req.params.pid);
|
|
150
|
+
const chatId = typeof req.query.chatId === 'string' ? req.query.chatId : '';
|
|
151
|
+
if (!Number.isFinite(pid)) {
|
|
152
|
+
res.status(400).json({ error: 'pid must be numeric' });
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
if (!chatId.trim()) {
|
|
156
|
+
res.status(400).json({ error: 'chatId is required' });
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
if (!(0, transient_children_1.killOwnedBackgroundProcess)(pid, { projectId: c.project.id, chatId: chatId.trim() })) {
|
|
160
|
+
res.status(404).json({ error: 'background process not found for project/chat' });
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
res.json({ ok: true, pid, status: 'killing' });
|
|
164
|
+
});
|
|
89
165
|
// Returns the resolved default model for Add Spec + the full provider
|
|
90
166
|
// allow-list so the modal can render its picker without maintaining its
|
|
91
167
|
// own copy of the model lists. Source of truth is `server/spec-models.ts`.
|
|
@@ -76,7 +76,7 @@ function registerSettingsRoutes(deps) {
|
|
|
76
76
|
res.json({ scope: merged });
|
|
77
77
|
});
|
|
78
78
|
router.patch('/:projectId/settings', (req, res) => {
|
|
79
|
-
const { pipelineTelemetryEnabled, orchestratorModel, prePrompt, freestylePrePrompt } = req.body ?? {};
|
|
79
|
+
const { pipelineTelemetryEnabled, orchestratorModel, prePrompt, freestylePrePrompt, worktreeEnvPassthrough } = req.body ?? {};
|
|
80
80
|
const patch = {};
|
|
81
81
|
if (pipelineTelemetryEnabled !== undefined) {
|
|
82
82
|
patch.pipelineTelemetryEnabled = Boolean(pipelineTelemetryEnabled);
|
|
@@ -103,6 +103,15 @@ function registerSettingsRoutes(deps) {
|
|
|
103
103
|
}
|
|
104
104
|
patch.freestylePrePrompt = freestylePrePrompt;
|
|
105
105
|
}
|
|
106
|
+
if (worktreeEnvPassthrough !== undefined) {
|
|
107
|
+
try {
|
|
108
|
+
patch.worktreeEnvPassthrough = (0, db_1.normalizeWorktreeEnvPassthrough)(worktreeEnvPassthrough);
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
res.status(400).json({ error: err instanceof Error ? err.message : 'invalid worktreeEnvPassthrough' });
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
106
115
|
if (req.body?.integrationBranch !== undefined) {
|
|
107
116
|
const ib = req.body.integrationBranch;
|
|
108
117
|
if (typeof ib !== 'string') {
|
|
@@ -33,6 +33,7 @@ const binary_probe_1 = require("./binary-probe");
|
|
|
33
33
|
const workspace_manager_1 = require("./workspace-manager");
|
|
34
34
|
const claude_trust_1 = require("./claude-trust");
|
|
35
35
|
const workspace_resolution_1 = require("./workspace-resolution");
|
|
36
|
+
const project_env_1 = require("./project-env");
|
|
36
37
|
const framework_manager_1 = require("./framework-manager");
|
|
37
38
|
const openspec_shim_1 = require("./openspec-shim");
|
|
38
39
|
const artifact_registry_1 = require("./artifact-registry");
|
|
@@ -1650,6 +1651,13 @@ class QueueManager {
|
|
|
1650
1651
|
// claude honours OTEL_* env vars natively; codex does not and instead
|
|
1651
1652
|
// gets signals synthesised by the codex-otel-bridge attached below.
|
|
1652
1653
|
let spawnEnv = process.env;
|
|
1654
|
+
// Per-project worktree/job env passthrough. The setting stores names only;
|
|
1655
|
+
// values are read from the server env (with login-shell recovery for missing
|
|
1656
|
+
// names) at spawn time. Apply this before Specrails' own env overlays so
|
|
1657
|
+
// internal SPECRAILS_* control-plane values always win.
|
|
1658
|
+
if (this._db) {
|
|
1659
|
+
spawnEnv = (0, project_env_1.applyWorktreeEnvPassthrough)(this._db, spawnEnv);
|
|
1660
|
+
}
|
|
1653
1661
|
const telemetryEnabled = !!(this._projectId && this._db && (0, db_1.getProjectSettings)(this._db).pipelineTelemetryEnabled);
|
|
1654
1662
|
// Resolve the framework version ONCE at spawn time — `framework/current`
|
|
1655
1663
|
// is read from `~/.specrails/framework/current`. A concurrent atomic swap
|
|
@@ -1668,7 +1676,7 @@ class QueueManager {
|
|
|
1668
1676
|
if (frameworkVersion)
|
|
1669
1677
|
extra['specrails.framework_version'] = frameworkVersion;
|
|
1670
1678
|
spawnEnv = {
|
|
1671
|
-
...
|
|
1679
|
+
...spawnEnv,
|
|
1672
1680
|
...buildTelemetryEnv(jobId, this._projectId, this._desktopPort, extra, adapter.id),
|
|
1673
1681
|
};
|
|
1674
1682
|
}
|
|
@@ -497,8 +497,9 @@ async function launchIsolatedRail(input, io = {}) {
|
|
|
497
497
|
// Build-settle: persist the per-unit branch outcomes + this launch's
|
|
498
498
|
// worktree ledger ids on the row (the deferred create-pr / discard actions
|
|
499
499
|
// reconstruct their inputs from these — nothing survives in memory), then
|
|
500
|
-
// hand the decision to the user. 0 succeeded units → nothing deliverable
|
|
501
|
-
//
|
|
500
|
+
// hand the decision to the user. 0 succeeded units → nothing deliverable;
|
|
501
|
+
// keep the implementation card visible as a failed job state with run-log
|
|
502
|
+
// chips instead of silently auto-discarding or leaving it at "building".
|
|
502
503
|
const worktreeIds = allocated.map((a) => a.ledgerId);
|
|
503
504
|
const anySucceeded = results.some((r) => r.succeeded);
|
|
504
505
|
const settledContinuation = anySucceeded ? launchContinuation : null;
|
|
@@ -522,7 +523,7 @@ async function launchIsolatedRail(input, io = {}) {
|
|
|
522
523
|
? 'pr_failed'
|
|
523
524
|
: (settledContinuation.isDraft === false ? 'pr_ready' : 'pr_draft'))
|
|
524
525
|
: 'on_review')
|
|
525
|
-
: '
|
|
526
|
+
: 'implementation_failed';
|
|
526
527
|
const patch = settledContinuation
|
|
527
528
|
? {
|
|
528
529
|
branches,
|
|
@@ -543,7 +544,7 @@ async function launchIsolatedRail(input, io = {}) {
|
|
|
543
544
|
syncOriginCard('update');
|
|
544
545
|
}
|
|
545
546
|
if (!anySucceeded) {
|
|
546
|
-
//
|
|
547
|
+
// FAILED-IMPLEMENTATION CLEANUP (0 succeeded): unmount every worktree NOW rather
|
|
547
548
|
// than leaving it for a restart's reconcile sweep — a still-mounted
|
|
548
549
|
// worktree POISONS the next run of the same ticket: the worktree path is
|
|
549
550
|
// keyed by ticketId, so the next allocation silently reuses this
|
|
@@ -107,7 +107,8 @@ function actionAllowed(action, row) {
|
|
|
107
107
|
return row.decision === 'pr_draft' && row.pr_url !== null;
|
|
108
108
|
case 'discard':
|
|
109
109
|
return row.decision === 'on_review' || row.decision === 'pr_draft' ||
|
|
110
|
-
row.decision === 'pr_ready' || row.decision === '
|
|
110
|
+
row.decision === 'pr_ready' || row.decision === 'implementation_failed' ||
|
|
111
|
+
row.decision === 'pr_failed';
|
|
111
112
|
case 'poll-merge':
|
|
112
113
|
return (row.decision === 'pr_draft' || row.decision === 'pr_ready') && row.pr_url !== null;
|
|
113
114
|
case 'merge-local':
|
|
@@ -438,9 +439,15 @@ async function runPublish(deps, row) {
|
|
|
438
439
|
}
|
|
439
440
|
async function runDiscard(deps, row) {
|
|
440
441
|
const snap = (0, rail_pr_store_1.toPrDeliverySnapshot)(row);
|
|
442
|
+
const implementationFailed = row.decision === 'implementation_failed';
|
|
441
443
|
// 1. Close the PR (drops its head branch) when one exists. Best-effort — a
|
|
442
444
|
// gh failure must never leave the discard half-done.
|
|
443
|
-
|
|
445
|
+
//
|
|
446
|
+
// Implementation-failed rows are different: the implement run produced no
|
|
447
|
+
// deliverable update. If the row belongs to an existing-PR continuation it may
|
|
448
|
+
// still carry that PR's url/branch from launch time; clearing the failed card
|
|
449
|
+
// must not close or delete the user's existing review branch.
|
|
450
|
+
if (row.pr_url && !implementationFailed) {
|
|
444
451
|
try {
|
|
445
452
|
const r = await deps.exec.run('gh', ['pr', 'close', row.pr_url, '--delete-branch'], deps.project.path);
|
|
446
453
|
if (r.code !== 0) {
|
|
@@ -454,12 +461,13 @@ async function runDiscard(deps, row) {
|
|
|
454
461
|
// 2. Remove the launch's worktrees (still on disk unless a restart already
|
|
455
462
|
// swept them) and close their ledger rows — the reconcile-style removal,
|
|
456
463
|
// branch deletion deferred to step 3 (a checked-out branch can't be -D'd).
|
|
457
|
-
const branchSet = new Set(snap.branches.map((b) => b.branch));
|
|
464
|
+
const branchSet = new Set(implementationFailed ? [] : snap.branches.map((b) => b.branch));
|
|
458
465
|
for (const wtId of snap.worktreeIds) {
|
|
459
466
|
const wt = (0, rail_worktrees_store_1.getRailWorktree)(deps.db, wtId);
|
|
460
467
|
if (!wt)
|
|
461
468
|
continue;
|
|
462
|
-
|
|
469
|
+
if (!implementationFailed)
|
|
470
|
+
branchSet.add(wt.branch);
|
|
463
471
|
await (0, worktree_manager_1.removeWorktree)(deps.git, {
|
|
464
472
|
repoDir: deps.project.path, worktreePath: wt.worktree_path, branch: wt.branch, deleteBranch: false,
|
|
465
473
|
}).catch(() => { });
|
|
@@ -471,22 +479,24 @@ async function runDiscard(deps, row) {
|
|
|
471
479
|
// best-effort from the same ticket data the create-pr path names it from
|
|
472
480
|
// (the delivered head is already covered by row.branch). Missing branches
|
|
473
481
|
// are tolerated; the integration branch is NEVER deleted.
|
|
474
|
-
if (
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
482
|
+
if (!implementationFailed) {
|
|
483
|
+
if (row.branch)
|
|
484
|
+
branchSet.add(row.branch);
|
|
485
|
+
const succeededUnits = snap.branches.filter((b) => b.succeeded);
|
|
486
|
+
if (succeededUnits.length > 1) {
|
|
487
|
+
try {
|
|
488
|
+
branchSet.add((0, pr_naming_1.batchBranchNameFor)(loadPrTicketData(deps, succeededUnits.map((b) => b.ticketId))));
|
|
489
|
+
}
|
|
490
|
+
catch {
|
|
491
|
+
/* best-effort defense-in-depth only */
|
|
492
|
+
}
|
|
480
493
|
}
|
|
481
|
-
|
|
482
|
-
|
|
494
|
+
for (const branch of branchSet) {
|
|
495
|
+
if (!branch || branch === row.base_branch)
|
|
496
|
+
continue;
|
|
497
|
+
await deps.git.run(['branch', '-D', branch], deps.project.path).catch(() => { });
|
|
483
498
|
}
|
|
484
499
|
}
|
|
485
|
-
for (const branch of branchSet) {
|
|
486
|
-
if (!branch || branch === row.base_branch)
|
|
487
|
-
continue;
|
|
488
|
-
await deps.git.run(['branch', '-D', branch], deps.project.path).catch(() => { });
|
|
489
|
-
}
|
|
490
500
|
const conflict = casTransition(deps, row, 'discarded');
|
|
491
501
|
if (conflict)
|
|
492
502
|
return conflict;
|
|
@@ -6,6 +6,7 @@ exports.createPrDelivery = createPrDelivery;
|
|
|
6
6
|
exports.getPrDelivery = getPrDelivery;
|
|
7
7
|
exports.getActivePrDeliveryByRail = getActivePrDeliveryByRail;
|
|
8
8
|
exports.listActivePrDeliveries = listActivePrDeliveries;
|
|
9
|
+
exports.reconcileFailedBuildingPrDeliveries = reconcileFailedBuildingPrDeliveries;
|
|
9
10
|
exports.transitionDecision = transitionDecision;
|
|
10
11
|
exports.toPrDeliverySnapshot = toPrDeliverySnapshot;
|
|
11
12
|
exports.toRailPrStateMessage = toRailPrStateMessage;
|
|
@@ -19,6 +20,7 @@ exports.ACTIVE_PR_DECISIONS = new Set([
|
|
|
19
20
|
'on_review',
|
|
20
21
|
'pr_draft',
|
|
21
22
|
'pr_ready',
|
|
23
|
+
'implementation_failed',
|
|
22
24
|
'pr_failed',
|
|
23
25
|
]);
|
|
24
26
|
function isTerminalPrDecision(s) {
|
|
@@ -60,6 +62,40 @@ function listActivePrDeliveries(db) {
|
|
|
60
62
|
ORDER BY rail_index, created_at DESC, rowid DESC`)
|
|
61
63
|
.all();
|
|
62
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* Recover implementation cards stranded at `building` after the loop process
|
|
67
|
+
* already settled all associated run ids without a success. This covers server
|
|
68
|
+
* restarts and the settle gap where loop_runs/worktrees mark failure but the
|
|
69
|
+
* PR-delivery row never receives its final card state.
|
|
70
|
+
*/
|
|
71
|
+
function reconcileFailedBuildingPrDeliveries(db) {
|
|
72
|
+
const rows = db
|
|
73
|
+
.prepare(`SELECT * FROM rail_pr_deliveries WHERE decision = 'building' ORDER BY created_at ASC, rowid ASC`)
|
|
74
|
+
.all();
|
|
75
|
+
const reconciled = [];
|
|
76
|
+
for (const row of rows) {
|
|
77
|
+
const runIds = parseJsonArray(row.run_ids ?? '[]');
|
|
78
|
+
if (runIds.length === 0)
|
|
79
|
+
continue;
|
|
80
|
+
const uniqueRunIds = [...new Set(runIds)];
|
|
81
|
+
const placeholders = uniqueRunIds.map(() => '?').join(',');
|
|
82
|
+
const runs = db
|
|
83
|
+
.prepare(`SELECT id, status, final_outcome FROM loop_runs WHERE id IN (${placeholders})`)
|
|
84
|
+
.all(...uniqueRunIds);
|
|
85
|
+
if (runs.length !== uniqueRunIds.length)
|
|
86
|
+
continue;
|
|
87
|
+
if (runs.some((run) => run.status !== 'completed'))
|
|
88
|
+
continue;
|
|
89
|
+
if (runs.some((run) => run.final_outcome === 'success'))
|
|
90
|
+
continue;
|
|
91
|
+
if (transitionDecision(db, row.id, 'building', 'implementation_failed')) {
|
|
92
|
+
const updated = getPrDelivery(db, row.id);
|
|
93
|
+
if (updated)
|
|
94
|
+
reconciled.push(updated);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return reconciled;
|
|
98
|
+
}
|
|
63
99
|
// Column allow-list — patch keys are interpolated into the SET clause, so gate
|
|
64
100
|
// them to prevent any future caller injecting SQL via an object key (values
|
|
65
101
|
// are always parameterized). Mirrors loop-runs-store's COUNTER_COLUMNS.
|
|
@@ -53,6 +53,11 @@ function emitPrDeliveryUpdate(c, prDeliveryId) {
|
|
|
53
53
|
(0, agent_chat_registry_1.getAgentChatManager)()?.updatePrDecisionCard(row.origin_conversation_id, (0, rail_pr_store_1.toPrDecisionCardEnvelope)(c.project.id, snap));
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
|
+
function reconcileAndEmitFailedPrDeliveries(c) {
|
|
57
|
+
for (const row of (0, rail_pr_store_1.reconcileFailedBuildingPrDeliveries)(c.db)) {
|
|
58
|
+
emitPrDeliveryUpdate(c, row.id);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
56
61
|
function createRailsRouter() {
|
|
57
62
|
const router = (0, express_1.Router)({ mergeParams: true });
|
|
58
63
|
function ctx(req) {
|
|
@@ -116,6 +121,7 @@ function createRailsRouter() {
|
|
|
116
121
|
// (non-terminal) delivery per rail slot, so a refreshed client re-renders
|
|
117
122
|
// the decision surface without waiting for a broadcast. The store lists
|
|
118
123
|
// newest-first within each rail — keep the first per index.
|
|
124
|
+
reconcileAndEmitFailedPrDeliveries(c);
|
|
119
125
|
const prDeliveries = {};
|
|
120
126
|
for (const row of (0, rail_pr_store_1.listActivePrDeliveries)(c.db)) {
|
|
121
127
|
if (!(row.rail_index in prDeliveries))
|
|
@@ -684,7 +690,7 @@ function createRailsRouter() {
|
|
|
684
690
|
if (current.pr_state === 'pr-created' && current.pr_url && current.branch) {
|
|
685
691
|
const succeeded = settled.every((s) => s.status === 'fulfilled' && s.value === true);
|
|
686
692
|
if (!succeeded) {
|
|
687
|
-
if ((0, rail_pr_store_1.transitionDecision)(c.db, fallbackPrDeliveryId, 'building', '
|
|
693
|
+
if ((0, rail_pr_store_1.transitionDecision)(c.db, fallbackPrDeliveryId, 'building', 'implementation_failed')) {
|
|
688
694
|
emitPrDeliveryUpdate(c, fallbackPrDeliveryId);
|
|
689
695
|
}
|
|
690
696
|
return;
|