specrails-desktop 2.12.0 → 2.13.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/README.md +3 -2
- package/client/dist/assets/ActivityFeedPage-D0-P3PsD.js +1 -0
- package/client/dist/assets/AgentsPage-BZa3lhQM.js +86 -0
- package/client/dist/assets/AnalyticsPage-CgaEg-lx.js +1 -0
- package/client/dist/assets/BarChart-D9vfeilz.js +33 -0
- package/client/dist/assets/CodePage-2GUsF_JS.js +2 -0
- package/client/dist/assets/DesktopAnalyticsPage-C6Qccm5E.js +1 -0
- package/client/dist/assets/DocsDialog-BAmtk9Jc.js +11 -0
- package/client/dist/assets/DocsPage-CJwnpB57.js +11 -0
- package/client/dist/assets/{ExportDropdown-I_8R48Yq.js → ExportDropdown-DMJ__Yui.js} +1 -1
- package/client/dist/assets/IntegrationsPage-CP2VcOpr.js +3 -0
- package/client/dist/assets/JobDetailPage-Bk6tQTew.js +16 -0
- package/client/dist/assets/JobsPage-BvbeqWjS.js +1 -0
- package/client/dist/assets/LoopBuilderPage-BS3enu9N.js +7 -0
- package/client/dist/assets/LoopBuilderPage-DtxX27Jz.css +1 -0
- package/client/dist/assets/LoopsPage-B0pgvoky.js +2 -0
- package/client/dist/assets/analytics-BHApBnUn.js +1 -0
- package/client/dist/assets/analytics-BUxrug9J.js +1 -0
- package/client/dist/assets/analytics-Cy6DTfO-.js +1 -0
- package/client/dist/assets/analytics-DMJxD9FD.js +1 -0
- package/client/dist/assets/analytics-DgrBuxla.js +1 -0
- package/client/dist/assets/analytics-DwxT1CS8.js +1 -0
- package/client/dist/assets/analytics-GrLc-SMr.js +1 -0
- package/client/dist/assets/analytics-qvPq4T4h.js +1 -0
- package/client/dist/assets/atom-one-dark-DrWoEEbZ.js +1 -0
- package/client/dist/assets/auth-Cvsb7-Ek.js +1 -0
- package/client/dist/assets/{cssMode-DzNPAYFh.js → cssMode-DSHXamUH.js} +1 -1
- package/client/dist/assets/dashboard-8FSyxKGV.js +1 -0
- package/client/dist/assets/dashboard-A9XQUfdS.js +1 -0
- package/client/dist/assets/dashboard-BFktSDsV.js +1 -0
- package/client/dist/assets/dashboard-C1PKCXLs.js +1 -0
- package/client/dist/assets/dashboard-DNf0czU-.js +1 -0
- package/client/dist/assets/dashboard-DQQCOVfu.js +1 -0
- package/client/dist/assets/dashboard-DxFqeHZk.js +1 -0
- package/client/dist/assets/dashboard-WvPGhbbi.js +1 -0
- package/client/dist/assets/{dist-js-DXfIbU9U.js → dist-js-_oLAsZQ4.js} +1 -1
- package/client/dist/assets/{dist-js-CtdZvl0R.js → dist-js-o4iwbCuN.js} +1 -1
- package/client/dist/assets/{editor.main-C7Rmw-hR.js → editor.main-Cd8qBRAT.js} +2 -2
- package/client/dist/assets/{freemarker2-Cszs4SVo.js → freemarker2-CkEKTBm2.js} +1 -1
- package/client/dist/assets/{handlebars-Dp7Lsuym.js → handlebars-BRGx0JpL.js} +1 -1
- package/client/dist/assets/{html-BURidrEm.js → html-BfRIaGgN.js} +1 -1
- package/client/dist/assets/{htmlMode--k5M7GjZ.js → htmlMode-BTpSnsDE.js} +1 -1
- package/client/dist/assets/index-DrFJinsI.css +2 -0
- package/client/dist/assets/index-QEeqwz4v.js +143 -0
- package/client/dist/assets/{javascript-kJQz__44.js → javascript-DMmE2kKN.js} +1 -1
- package/client/dist/assets/{jsonMode-v5JYPpnz.js → jsonMode-hYuaUleY.js} +1 -1
- package/client/dist/assets/{lib-BBoUS-5a.js → lib-CVrpo1ac.js} +2 -2
- package/client/dist/assets/{liquid-Dl9I6gWt.js → liquid-C-VbuTr7.js} +1 -1
- package/client/dist/assets/loops-B1ZQCKEk.js +1 -0
- package/client/dist/assets/loops-Bp5yTSic.js +1 -0
- package/client/dist/assets/loops-BvoL_LAc.js +1 -0
- package/client/dist/assets/loops-BwgehdXY.js +1 -0
- package/client/dist/assets/loops-CMLUqUpc.js +1 -0
- package/client/dist/assets/loops-CW69VvTI.js +1 -0
- package/client/dist/assets/loops-DkV2RhEb.js +1 -0
- package/client/dist/assets/loops-arQ602Zm.js +1 -0
- package/client/dist/assets/{lspLanguageFeatures-CPlEe0NK.js → lspLanguageFeatures-BALx-vjn.js} +1 -1
- package/client/dist/assets/{mdx-Byl7TtzQ.js → mdx-Cs8MNCzn.js} +1 -1
- package/client/dist/assets/{monaco.contribution-YMAkHQcQ.js → monaco.contribution-CIvCAJs4.js} +2 -2
- package/client/dist/assets/nav-B5W9IMnf.js +1 -0
- package/client/dist/assets/nav-CCz4krPP.js +1 -0
- package/client/dist/assets/nav-CrG_mYRr.js +1 -0
- package/client/dist/assets/nav-Cw3_40fA.js +1 -0
- package/client/dist/assets/nav-DNDJ3H-_.js +1 -0
- package/client/dist/assets/nav-DVp1nEsi.js +1 -0
- package/client/dist/assets/nav-DbAbG5_k.js +1 -0
- package/client/dist/assets/nav-_0vAgZ_k.js +1 -0
- package/client/dist/assets/{python-jWQwT6j2.js → python-CGShLxuI.js} +1 -1
- package/client/dist/assets/{razor-BWS3sP-E.js → razor-CvOBQGd5.js} +1 -1
- package/client/dist/assets/{setup-Cpu17hJv.js → setup-BOmQ4vh2.js} +1 -1
- package/client/dist/assets/setup-BwO2-kuK.js +1 -0
- package/client/dist/assets/{setup-D-1r0uSx.js → setup-CUTNWz-0.js} +1 -1
- package/client/dist/assets/{setup-BHroXlke.js → setup-Cf3hjChU.js} +1 -1
- package/client/dist/assets/{setup-BIXsWUp1.js → setup-CsuXrbJ2.js} +1 -1
- package/client/dist/assets/{setup-B6egeeTM.js → setup-Df4cJOqu.js} +1 -1
- package/client/dist/assets/{setup-C0rVGnCy.js → setup-Do0fWx6W.js} +1 -1
- package/client/dist/assets/{setup-BJRdg1iE.js → setup-GlkDhBlJ.js} +1 -1
- package/client/dist/assets/{tsMode-BbOGOuSV.js → tsMode-04uOpiiP.js} +1 -1
- package/client/dist/assets/{typescript-eBtFQJLs.js → typescript-CX0CwyIZ.js} +1 -1
- package/client/dist/assets/upload-Dn9N1ESQ.js +1 -0
- package/client/dist/assets/value-Bd3Cf9Sr.js +1 -0
- package/client/dist/assets/{workers-BvicOoDf.js → workers-yr2u4c3C.js} +1 -1
- package/client/dist/assets/{xml-BJepAPyM.js → xml-C0OFlt5X.js} +1 -1
- package/client/dist/assets/{yaml-DabgV-eA.js → yaml-DgabaPs5.js} +1 -1
- package/client/dist/index.html +28 -26
- package/docs/getting-started.md +3 -2
- package/docs/guide/de/pipeline/1-rails-and-jobs.md +27 -22
- package/docs/guide/de/pipeline/5-the-loop-builder.md +91 -0
- package/docs/guide/en/pipeline/1-rails-and-jobs.md +11 -6
- package/docs/guide/en/pipeline/5-the-loop-builder.md +91 -0
- package/docs/guide/es/pipeline/1-rails-and-jobs.md +11 -6
- package/docs/guide/es/pipeline/5-the-loop-builder.md +91 -0
- package/docs/guide/fr/pipeline/1-rails-and-jobs.md +40 -35
- package/docs/guide/fr/pipeline/5-the-loop-builder.md +91 -0
- package/docs/guide/it/pipeline/1-rails-and-jobs.md +11 -6
- package/docs/guide/it/pipeline/5-the-loop-builder.md +91 -0
- package/docs/guide/ja/pipeline/1-rails-and-jobs.md +11 -6
- package/docs/guide/ja/pipeline/5-the-loop-builder.md +91 -0
- package/docs/guide/pt/pipeline/1-rails-and-jobs.md +50 -45
- package/docs/guide/pt/pipeline/5-the-loop-builder.md +91 -0
- package/docs/guide/zh/pipeline/1-rails-and-jobs.md +13 -8
- package/docs/guide/zh/pipeline/5-the-loop-builder.md +91 -0
- package/docs/internals/companion-rails-as-loops-contract.md +199 -0
- package/docs/internals/global-artifacts-alignment-contract.md +2 -0
- package/docs/running-pipelines.md +25 -14
- package/package.json +1 -1
- package/server/dist/ai-invocations.js +5 -3
- package/server/dist/db.js +45 -0
- package/server/dist/desktop-db.js +48 -0
- package/server/dist/desktop-router.js +10 -0
- package/server/dist/feature-flags.js +13 -0
- package/server/dist/loop-command-catalog.js +145 -0
- package/server/dist/loop-constants.js +82 -0
- package/server/dist/loop-decider.js +66 -0
- package/server/dist/loop-executors.js +193 -0
- package/server/dist/loop-factory.js +56 -0
- package/server/dist/loop-graph.js +144 -0
- package/server/dist/loop-preview.js +69 -0
- package/server/dist/loop-run-manager.js +407 -0
- package/server/dist/loop-runs-store.js +69 -0
- package/server/dist/loop-templates.js +137 -0
- package/server/dist/loops-router.js +302 -0
- package/server/dist/loops-store.js +137 -0
- package/server/dist/mobile/mobile-router.js +22 -0
- package/server/dist/mobile/mobile-ws.js +17 -1
- package/server/dist/project-registry.js +103 -1
- package/server/dist/project-router-jobs.js +11 -1
- package/server/dist/project-router-loop-runs.js +102 -0
- package/server/dist/project-router.js +2 -0
- package/server/dist/providers/claude-adapter.js +4 -0
- package/server/dist/providers/codex-adapter.js +21 -0
- package/server/dist/providers/gemini-adapter.js +4 -0
- package/server/dist/rails-router.js +175 -6
- package/server/dist/spending.js +5 -2
- package/server/dist/util/cli-prompt.js +6 -3
- package/client/dist/assets/ActivityFeedPage-B9-UAGS7.js +0 -1
- package/client/dist/assets/AgentsPage-Bz9rophT.js +0 -86
- package/client/dist/assets/AnalyticsPage-D3ynQwGd.js +0 -1
- package/client/dist/assets/BarChart-Cwp6VuED.js +0 -33
- package/client/dist/assets/CodePage-CO_TLOvi.js +0 -2
- package/client/dist/assets/DesktopAnalyticsPage-BMDqiAbw.js +0 -1
- package/client/dist/assets/DocsDialog-B-chJn-f.js +0 -11
- package/client/dist/assets/DocsPage-BcJwYo20.js +0 -11
- package/client/dist/assets/IntegrationsPage-Bd4l3L6u.js +0 -3
- package/client/dist/assets/JobDetailPage-Dt3jLjS4.js +0 -16
- package/client/dist/assets/JobsPage-DcWpJbBc.js +0 -1
- package/client/dist/assets/analytics-BeTyviO8.js +0 -1
- package/client/dist/assets/analytics-C4eEO260.js +0 -1
- package/client/dist/assets/analytics-C67cIA1b.js +0 -1
- package/client/dist/assets/analytics-CAguvW28.js +0 -1
- package/client/dist/assets/analytics-DBtt8Mgk.js +0 -1
- package/client/dist/assets/analytics-DMCto-TF.js +0 -1
- package/client/dist/assets/analytics-DUPtODxX.js +0 -1
- package/client/dist/assets/analytics-YIpQvPAc.js +0 -1
- package/client/dist/assets/dashboard--Ahnvfr3.js +0 -1
- package/client/dist/assets/dashboard-BN1C2pEh.js +0 -1
- package/client/dist/assets/dashboard-BZs_EzAn.js +0 -1
- package/client/dist/assets/dashboard-Bsw44L8_.js +0 -1
- package/client/dist/assets/dashboard-Bw3VECgY.js +0 -1
- package/client/dist/assets/dashboard-CuOshSHn.js +0 -1
- package/client/dist/assets/dashboard-DfouCM3_.js +0 -1
- package/client/dist/assets/dashboard-Pp5hwnZB.js +0 -1
- package/client/dist/assets/index-BEGU2TCx.js +0 -143
- package/client/dist/assets/index-B_Zqeoa5.css +0 -2
- package/client/dist/assets/nav-B05EYB0b.js +0 -1
- package/client/dist/assets/nav-BNGCq-0y.js +0 -1
- package/client/dist/assets/nav-BRInPX8a.js +0 -1
- package/client/dist/assets/nav-Bf87DRHD.js +0 -1
- package/client/dist/assets/nav-BkVzzFpc.js +0 -1
- package/client/dist/assets/nav-BzFLtS1W.js +0 -1
- package/client/dist/assets/nav-DBDbQOYn.js +0 -1
- package/client/dist/assets/nav-X9sVtUWC.js +0 -1
- package/client/dist/assets/setup-Dn2-veYO.js +0 -1
- /package/client/dist/assets/{abap-s65oMlhi.js → abap-CK68cr9-.js} +0 -0
- /package/client/dist/assets/{activity-Cyy07Tgo.js → activity-B2MvEH54.js} +0 -0
- /package/client/dist/assets/{activity-qFTcMyW9.js → activity-BbNkUmW4.js} +0 -0
- /package/client/dist/assets/{activity-DlbWCa4y.js → activity-C7VdmeGb.js} +0 -0
- /package/client/dist/assets/{activity-Dv6H7wEr.js → activity-Ca2XoBi-.js} +0 -0
- /package/client/dist/assets/{activity-C8qqEIoP.js → activity-D4dhNEwO.js} +0 -0
- /package/client/dist/assets/{activity-Dwq0heud.js → activity-DHjfmAyT.js} +0 -0
- /package/client/dist/assets/{activity-CZVM4nlJ.js → activity-Di2f50_Z.js} +0 -0
- /package/client/dist/assets/{activity-BqqwnH_h.js → activity-usnNa7FX.js} +0 -0
- /package/client/dist/assets/{addon-image-CpF0L0jM.js → addon-image-biWX2UUm.js} +0 -0
- /package/client/dist/assets/{addon-ligatures-hXysGZrA.js → addon-ligatures-D4EiDb5r.js} +0 -0
- /package/client/dist/assets/{addon-webgl-Cn1slavz.js → addon-webgl-CDCIZQ03.js} +0 -0
- /package/client/dist/assets/{addspec-RuL8Zd7w.js → addspec-1HEtmLv1.js} +0 -0
- /package/client/dist/assets/{addspec-DDvvnE6N.js → addspec-BxMn39wn.js} +0 -0
- /package/client/dist/assets/{addspec-xjDbYZWL.js → addspec-CNyAUGz_.js} +0 -0
- /package/client/dist/assets/{addspec-CIGb34PS.js → addspec-CO1xv6BL.js} +0 -0
- /package/client/dist/assets/{addspec-BErjOdNK.js → addspec-DAveBiCE.js} +0 -0
- /package/client/dist/assets/{addspec-C_3NBarY.js → addspec-DE_VfIyP.js} +0 -0
- /package/client/dist/assets/{addspec-B-BKlvDj.js → addspec-DfuR6EAl.js} +0 -0
- /package/client/dist/assets/{addspec-rmhOaH7N.js → addspec-IozoiiGs.js} +0 -0
- /package/client/dist/assets/{agents-BDx1RXcl.js → agents-1lhqEcXH.js} +0 -0
- /package/client/dist/assets/{agents-BFr3kUhK.js → agents-BYjUOrLD.js} +0 -0
- /package/client/dist/assets/{agents-CMxtJMLD.js → agents-CIhRSOna.js} +0 -0
- /package/client/dist/assets/{agents-DcxZHzNr.js → agents-CQlu1nOm.js} +0 -0
- /package/client/dist/assets/{agents-BlPnx-mz.js → agents-DCX9D3-Q.js} +0 -0
- /package/client/dist/assets/{agents-23iPejcA.js → agents-DVYpVGur.js} +0 -0
- /package/client/dist/assets/{agents-B_1L9xRg.js → agents-DcoUUFZl.js} +0 -0
- /package/client/dist/assets/{agents-G3shOewU.js → agents-Jc-CjBDB.js} +0 -0
- /package/client/dist/assets/{agentstudio-kk9RB7Se.js → agentstudio-1E-bag77.js} +0 -0
- /package/client/dist/assets/{agentstudio-Bk1eZcv4.js → agentstudio-9NbsoGXk.js} +0 -0
- /package/client/dist/assets/{agentstudio-Y3G0ddJ2.js → agentstudio-B-jQgYV-.js} +0 -0
- /package/client/dist/assets/{agentstudio-B-CMAQqy.js → agentstudio-B1y0JWY-.js} +0 -0
- /package/client/dist/assets/{agentstudio-DpP9caEE.js → agentstudio-Bwa72b1S.js} +0 -0
- /package/client/dist/assets/{agentstudio-CxlUllqI.js → agentstudio-DCTpIqOx.js} +0 -0
- /package/client/dist/assets/{agentstudio-DNlme601.js → agentstudio-DCyDW8sy.js} +0 -0
- /package/client/dist/assets/{agentstudio-ChxNuGAu.js → agentstudio-Q0Sq_jTg.js} +0 -0
- /package/client/dist/assets/{aiedit-DrfzQroF.js → aiedit-CGxeml8D.js} +0 -0
- /package/client/dist/assets/{aiedit-De0SOH6S.js → aiedit-CfBNanHE.js} +0 -0
- /package/client/dist/assets/{aiedit-BBCrOpHq.js → aiedit-CvSspQmE.js} +0 -0
- /package/client/dist/assets/{aiedit-fMltW101.js → aiedit-DSLtsYIu.js} +0 -0
- /package/client/dist/assets/{aiedit-BMtcGYNE.js → aiedit-DUpe7NxI.js} +0 -0
- /package/client/dist/assets/{aiedit-TTwzL1TS.js → aiedit-Dirqf3Bm.js} +0 -0
- /package/client/dist/assets/{aiedit-5ETerMK1.js → aiedit-kAU7KADg.js} +0 -0
- /package/client/dist/assets/{aiedit-D9ddlgkM.js → aiedit-qTGI_PGs.js} +0 -0
- /package/client/dist/assets/{apex-BLUBIldB.js → apex-BHHyAVNb.js} +0 -0
- /package/client/dist/assets/{attachments-CHaDUfjB.js → attachments-3CuBBRFu.js} +0 -0
- /package/client/dist/assets/{attachments-DazTVJbH.js → attachments-BLqqPGyy.js} +0 -0
- /package/client/dist/assets/{attachments-CCWasu-P.js → attachments-BNgH6Tcu.js} +0 -0
- /package/client/dist/assets/{attachments-BIsSSnHJ.js → attachments-C2JPppoP.js} +0 -0
- /package/client/dist/assets/{attachments-CVSAbGNl.js → attachments-CppM7de8.js} +0 -0
- /package/client/dist/assets/{attachments-Chg5poG1.js → attachments-D5fVsMgb.js} +0 -0
- /package/client/dist/assets/{attachments-Dn-JImAK.js → attachments-UJtEq892.js} +0 -0
- /package/client/dist/assets/{attachments-LDA9kp2X.js → attachments-pW3zm9nu.js} +0 -0
- /package/client/dist/assets/{azcli-DuWxh9mO.js → azcli-C56lfIEP.js} +0 -0
- /package/client/dist/assets/{bat-UKoTejQm.js → bat-BLamSaf-.js} +0 -0
- /package/client/dist/assets/{bicep-4sTT4B3D.js → bicep-lrOcEItS.js} +0 -0
- /package/client/dist/assets/{browser-D2Y_UAKA.js → browser-B7i7Ysc8.js} +0 -0
- /package/client/dist/assets/{browser-BDd1dbFa.js → browser-BVQgd1_1.js} +0 -0
- /package/client/dist/assets/{browser-Dxc_VIRK.js → browser-C91MQBxx.js} +0 -0
- /package/client/dist/assets/{browser-DH9SGVfM.js → browser-C__Vz7KR.js} +0 -0
- /package/client/dist/assets/{browser-lTQwcDCI.js → browser-DhwTc0ry.js} +0 -0
- /package/client/dist/assets/{browser-Qya9cARy.js → browser-Dpo_ug4c.js} +0 -0
- /package/client/dist/assets/{browser-BWSgbfdX.js → browser-DwE289IS.js} +0 -0
- /package/client/dist/assets/{browser-DWOVYMlg.js → browser-EFmPpkvO.js} +0 -0
- /package/client/dist/assets/{cameligo-CAAryRYO.js → cameligo-BK_p0bgU.js} +0 -0
- /package/client/dist/assets/{chat-UVVZqA57.js → chat-9RcD5Lcn.js} +0 -0
- /package/client/dist/assets/{chat-CUrG1eVg.js → chat-BXVx6rb5.js} +0 -0
- /package/client/dist/assets/{chat-CPgmgZOj.js → chat-BnJPyeUK.js} +0 -0
- /package/client/dist/assets/{chat-CvOOKB2s.js → chat-C5nJF6L0.js} +0 -0
- /package/client/dist/assets/{chat-BO9MvVID.js → chat-CE8bx6Rz.js} +0 -0
- /package/client/dist/assets/{chat-DIh3hr6y.js → chat-PVakYp92.js} +0 -0
- /package/client/dist/assets/{chat-mPn3UlMl.js → chat-UMl6H55K.js} +0 -0
- /package/client/dist/assets/{chat-BsbNGPW9.js → chat-mq1x9cxZ.js} +0 -0
- /package/client/dist/assets/{clojure-BlMERO1w.js → clojure-BQ7JOykv.js} +0 -0
- /package/client/dist/assets/{clsx-CnH-HMk3.js → clsx-CjLeUJfG.js} +0 -0
- /package/client/dist/assets/{code-BwIz8agY.js → code-BPMR8_Vp.js} +0 -0
- /package/client/dist/assets/{code-CDFlxUFC.js → code-BsOBm1Fb.js} +0 -0
- /package/client/dist/assets/{code-CY85RXZU.js → code-DtIatVES.js} +0 -0
- /package/client/dist/assets/{code-DtZBQTi9.js → code-Lg4awa35.js} +0 -0
- /package/client/dist/assets/{code-CD7yNSK0.js → code-OoLqhjoI.js} +0 -0
- /package/client/dist/assets/{code-D24e1Crx.js → code-aCUqkVN_.js} +0 -0
- /package/client/dist/assets/{code-Cp3Fdng-.js → code-eh4vO2-_.js} +0 -0
- /package/client/dist/assets/{code-nKa0fkm_.js → code-u9Sg9ohM.js} +0 -0
- /package/client/dist/assets/{coffee-Cj8D-Wl1.js → coffee-BD3DiL5s.js} +0 -0
- /package/client/dist/assets/{commands-t4frzhB0.js → commands-B9XEyMj-.js} +0 -0
- /package/client/dist/assets/{commands-BR1kDkHQ.js → commands-BI1Ta-E7.js} +0 -0
- /package/client/dist/assets/{commands-DWgp-8W1.js → commands-BdI0CtOL.js} +0 -0
- /package/client/dist/assets/{commands-ddsl1V91.js → commands-C2ewlyXo.js} +0 -0
- /package/client/dist/assets/{commands-IXMOKBYt.js → commands-CwypztRT.js} +0 -0
- /package/client/dist/assets/{commands-Cb21pDlG.js → commands-DSX3yJyr.js} +0 -0
- /package/client/dist/assets/{commands-B-MVT-2F.js → commands-Ic1DJDxm.js} +0 -0
- /package/client/dist/assets/{commands-B0yFTp7e.js → commands-yNn4IA4C.js} +0 -0
- /package/client/dist/assets/{common-CTEbWVZS.js → common-BhFasU8N.js} +0 -0
- /package/client/dist/assets/{common-CALKUpYm.js → common-CnrZ9gCZ.js} +0 -0
- /package/client/dist/assets/{common-B4sqsKp7.js → common-CwhHWbTD.js} +0 -0
- /package/client/dist/assets/{common-wA36jmj1.js → common-D9GAPxeN.js} +0 -0
- /package/client/dist/assets/{common-5ilvMOcH.js → common-DI4jE5y-.js} +0 -0
- /package/client/dist/assets/{common-DQiza2Xp.js → common-DS8zKLQT.js} +0 -0
- /package/client/dist/assets/{common-BKpVwUIf.js → common-DTHzyfYC.js} +0 -0
- /package/client/dist/assets/{common-BzEC3kJU.js → common-DY4nzbWC.js} +0 -0
- /package/client/dist/assets/{cpp-BPfKnaj_.js → cpp-splnCwCh.js} +0 -0
- /package/client/dist/assets/{csharp-gX-x5uD6.js → csharp-BCpsl-o3.js} +0 -0
- /package/client/dist/assets/{csp-DKGVt8SM.js → csp-tkCWrZmb.js} +0 -0
- /package/client/dist/assets/{css-CPMdnAVq.js → css-B_xdnReq.js} +0 -0
- /package/client/dist/assets/{cypher-ClMDrj9S.js → cypher-OL1zq-y3.js} +0 -0
- /package/client/dist/assets/{dart-C4zbzpVv.js → dart-CtTlOkrm.js} +0 -0
- /package/client/dist/assets/{dockerfile-D9xw73D1.js → dockerfile-DcQ5CNiW.js} +0 -0
- /package/client/dist/assets/{ecl-gqO8tIR9.js → ecl-DZpiDRt6.js} +0 -0
- /package/client/dist/assets/{editor.api2-BPnIxMjz.js → editor.api2-Cwla1nvV.js} +0 -0
- /package/client/dist/assets/{elixir-DSAhVF3_.js → elixir-Dzj2lqtK.js} +0 -0
- /package/client/dist/assets/{explore-8gMBVmkp.js → explore-4SD1DxR0.js} +0 -0
- /package/client/dist/assets/{explore-vdBm_Re8.js → explore-B2n8r9Ye.js} +0 -0
- /package/client/dist/assets/{explore-BkWTnLi3.js → explore-BxR9q8Hq.js} +0 -0
- /package/client/dist/assets/{explore-CQUCw9ci.js → explore-CBYU76JD.js} +0 -0
- /package/client/dist/assets/{explore-Dbac0ErD.js → explore-CCcPXrHg.js} +0 -0
- /package/client/dist/assets/{explore-DUU36tcH.js → explore-CZt4Ncpa.js} +0 -0
- /package/client/dist/assets/{explore-DlKgN7D-.js → explore-DQ0XEjed.js} +0 -0
- /package/client/dist/assets/{explore-COaUKI0t.js → explore-xQoaak6e.js} +0 -0
- /package/client/dist/assets/{flow9-DeQCSPOd.js → flow9-CFENCHIZ.js} +0 -0
- /package/client/dist/assets/{format-command-CZsy-vHq.js → format-command-CMsBTOPN.js} +0 -0
- /package/client/dist/assets/{fsharp-CEfaXL-S.js → fsharp-D-4bv7FB.js} +0 -0
- /package/client/dist/assets/{go-Xp1OkZCh.js → go-djxbTJq7.js} +0 -0
- /package/client/dist/assets/{graphql-BwRXrUwe.js → graphql-DHIFrkZ6.js} +0 -0
- /package/client/dist/assets/{hcl-u06DtVFk.js → hcl-CXVoowNb.js} +0 -0
- /package/client/dist/assets/{ini-AmeIpFND.js → ini-Crzc5kly.js} +0 -0
- /package/client/dist/assets/{integrations-B9CEpNF0.js → integrations-9qxUNhPD.js} +0 -0
- /package/client/dist/assets/{integrations-DWz1eU_K.js → integrations-A9f08E0o.js} +0 -0
- /package/client/dist/assets/{integrations-BlvAdewo.js → integrations-B17KyA-i.js} +0 -0
- /package/client/dist/assets/{integrations-Bw8UM9Xd.js → integrations-B46ac3sf.js} +0 -0
- /package/client/dist/assets/{integrations-7YyTBuU9.js → integrations-C7u7SInG.js} +0 -0
- /package/client/dist/assets/{integrations-DiPR8Fzp.js → integrations-DkBi-Gsh.js} +0 -0
- /package/client/dist/assets/{integrations-C5SxNKnG.js → integrations-DwSVdCpf.js} +0 -0
- /package/client/dist/assets/{integrations-CJQKMmdW.js → integrations-jJGaKwff.js} +0 -0
- /package/client/dist/assets/{java-CyDbRQjX.js → java-CYLaJrdE.js} +0 -0
- /package/client/dist/assets/{jira-DOw8bkIR.js → jira-01H1RfQj.js} +0 -0
- /package/client/dist/assets/{jira-DKImM1YH.js → jira-0ldfMByG.js} +0 -0
- /package/client/dist/assets/{jira-Fob8EGxN.js → jira-4Fin3fu7.js} +0 -0
- /package/client/dist/assets/{jira-xZA2lixb.js → jira-BpnpUFlb.js} +0 -0
- /package/client/dist/assets/{jira-C-ATCti0.js → jira-BwACz875.js} +0 -0
- /package/client/dist/assets/{jira-DlA-wGp-.js → jira-CBU0200d.js} +0 -0
- /package/client/dist/assets/{jira-D7bkKAX8.js → jira-Dht8qNck.js} +0 -0
- /package/client/dist/assets/{jira-CmVfRM-b.js → jira-DzIVvNmy.js} +0 -0
- /package/client/dist/assets/{jobs-DAF8AGy5.js → jobs-BF9raUkL.js} +0 -0
- /package/client/dist/assets/{jobs-D93lG6If.js → jobs-Bw4hqEro.js} +0 -0
- /package/client/dist/assets/{jobs-F5PGJwbW.js → jobs-DM9owF1j.js} +0 -0
- /package/client/dist/assets/{jobs-Db3xrsp_.js → jobs-DRInW0AE.js} +0 -0
- /package/client/dist/assets/{jobs-fYWWxCUV.js → jobs-DcCONARH.js} +0 -0
- /package/client/dist/assets/{jobs-Brp44JDd.js → jobs-Oz3UtLQn.js} +0 -0
- /package/client/dist/assets/{jobs-Do4Ltqdj.js → jobs-jEQidSIb.js} +0 -0
- /package/client/dist/assets/{jobs-BGkI19S_.js → jobs-ym72SIR9.js} +0 -0
- /package/client/dist/assets/{julia-BqialFRG.js → julia-BJ7tUU-T.js} +0 -0
- /package/client/dist/assets/{kotlin-Dzz8TWAt.js → kotlin-CeWcU3lC.js} +0 -0
- /package/client/dist/assets/{less-DHRJD3TR.js → less-CviwWNG4.js} +0 -0
- /package/client/dist/assets/{lexon-5Y3QgTmT.js → lexon-Dt28jqDS.js} +0 -0
- /package/client/dist/assets/{lua-sKvhfPn5.js → lua-B6AOaJSi.js} +0 -0
- /package/client/dist/assets/{m3-DWDVwkFG.js → m3-VnMkHJcy.js} +0 -0
- /package/client/dist/assets/{markdown-CD_aSBxW.js → markdown-8vKNdvIM.js} +0 -0
- /package/client/dist/assets/{mips-687T03hg.js → mips-DoEcuzan.js} +0 -0
- /package/client/dist/assets/{msdax-C1St-dIV.js → msdax-CNzvHyEp.js} +0 -0
- /package/client/dist/assets/{mysql-BG7r8oBS.js → mysql-Buk3vUtL.js} +0 -0
- /package/client/dist/assets/{objective-c-Ds1-m05L.js → objective-c-DfOmda-5.js} +0 -0
- /package/client/dist/assets/{pascal-BKK9FpIi.js → pascal-DoAA2yjy.js} +0 -0
- /package/client/dist/assets/{pascaligo-SRS3nwtO.js → pascaligo-C65Vn1fu.js} +0 -0
- /package/client/dist/assets/{perl-B2hTOlrF.js → perl-INlfQ_ro.js} +0 -0
- /package/client/dist/assets/{pgsql-DIQJYNpL.js → pgsql-DtwRzglx.js} +0 -0
- /package/client/dist/assets/{php-BEaVe8X2.js → php-DfHqVNet.js} +0 -0
- /package/client/dist/assets/{pla-oPLHpZ-Q.js → pla-D3dNVRYk.js} +0 -0
- /package/client/dist/assets/{postiats-D_vzrAzD.js → postiats-UKXD1rM4.js} +0 -0
- /package/client/dist/assets/{powerquery-BKG6w-FH.js → powerquery-zE3E3O7s.js} +0 -0
- /package/client/dist/assets/{powershell-B3dLhDt4.js → powershell-Df9b3zQl.js} +0 -0
- /package/client/dist/assets/{protobuf-DC8SGjcl.js → protobuf-BF0nUVrW.js} +0 -0
- /package/client/dist/assets/{pug-D5E-4fI0.js → pug-Bj6Yikv_.js} +0 -0
- /package/client/dist/assets/{qsharp-6vJAWv0x.js → qsharp-BI4h-raZ.js} +0 -0
- /package/client/dist/assets/{r-CDwsEcbM.js → r-DhpnmaXx.js} +0 -0
- /package/client/dist/assets/{redis-CuQbbESS.js → redis-DwYYPr4d.js} +0 -0
- /package/client/dist/assets/{redshift-B9e1k-qI.js → redshift-D2f3Bc-4.js} +0 -0
- /package/client/dist/assets/{restructuredtext-BiJ5IwaU.js → restructuredtext-Dkiif6fe.js} +0 -0
- /package/client/dist/assets/{ruby-B0UAHY9b.js → ruby-C6hLmHy0.js} +0 -0
- /package/client/dist/assets/{rust-Dg_spmFr.js → rust-X8WAGtxB.js} +0 -0
- /package/client/dist/assets/{sb-DjU66I8Q.js → sb-4M2la0Kr.js} +0 -0
- /package/client/dist/assets/{scala-qvStIdfG.js → scala-CaCXz58E.js} +0 -0
- /package/client/dist/assets/{scheme-FstEk5Rh.js → scheme-CMdjpmP7.js} +0 -0
- /package/client/dist/assets/{scss-w0U3rQLK.js → scss-QopmvPyp.js} +0 -0
- /package/client/dist/assets/{settings-CewqtD9V.js → settings-0m2iuTzd.js} +0 -0
- /package/client/dist/assets/{settings-Puea5c8v.js → settings-BA1V6KdK.js} +0 -0
- /package/client/dist/assets/{settings-Cx97tHs5.js → settings-BN7OusER.js} +0 -0
- /package/client/dist/assets/{settings-BwMQ1lx5.js → settings-Bi5FFkIw.js} +0 -0
- /package/client/dist/assets/{settings-EMaKBwE0.js → settings-CRJxyTNN.js} +0 -0
- /package/client/dist/assets/{settings-DkB9Wh-f.js → settings-DyBxunCh.js} +0 -0
- /package/client/dist/assets/{settings-CqCY8gZq.js → settings-fk-WlnUt.js} +0 -0
- /package/client/dist/assets/{settings-CyixAx0X.js → settings-iQe5Dim-.js} +0 -0
- /package/client/dist/assets/{shell-DJ78wREd.js → shell-CDBchS00.js} +0 -0
- /package/client/dist/assets/{solidity-1aGIVsdX.js → solidity-DvBfi5mC.js} +0 -0
- /package/client/dist/assets/{sophia-40LqcGjB.js → sophia-HSnXR17u.js} +0 -0
- /package/client/dist/assets/{sparql-Cz5dqG_g.js → sparql-DhG_ppup.js} +0 -0
- /package/client/dist/assets/{specs-DfwDeADE.js → specs-Bku8SypT.js} +0 -0
- /package/client/dist/assets/{specs-B4GuOzuZ.js → specs-BvsFNKTs.js} +0 -0
- /package/client/dist/assets/{specs-ghyBMnib.js → specs-C2wW5bq8.js} +0 -0
- /package/client/dist/assets/{specs-VK-zXv7x.js → specs-CUE3xSFM.js} +0 -0
- /package/client/dist/assets/{specs-D-Sb6dre.js → specs-C_8j4ELZ.js} +0 -0
- /package/client/dist/assets/{specs-C62F2CDv.js → specs-CnH7dOiF.js} +0 -0
- /package/client/dist/assets/{specs-DFSkAeK8.js → specs-D4ORogcT.js} +0 -0
- /package/client/dist/assets/{specs-BVLKe2n5.js → specs-D6Y76Bgi.js} +0 -0
- /package/client/dist/assets/{sql-64f62Ni4.js → sql-DHeIS-tf.js} +0 -0
- /package/client/dist/assets/{st-gJe2yG8J.js → st-CMWchiRU.js} +0 -0
- /package/client/dist/assets/{swift-C6ME22mv.js → swift-CDXvBefj.js} +0 -0
- /package/client/dist/assets/{systemverilog-CEWz259w.js → systemverilog-0RTukYUE.js} +0 -0
- /package/client/dist/assets/{tcl-CcLVIi3m.js → tcl-CbBF_Ttl.js} +0 -0
- /package/client/dist/assets/{terminal-CPpK58RC.js → terminal-BLh00ouM.js} +0 -0
- /package/client/dist/assets/{terminal-CdxkpafL.js → terminal-BTanJIt_.js} +0 -0
- /package/client/dist/assets/{terminal-DHIkiWcs.js → terminal-CB33IsO1.js} +0 -0
- /package/client/dist/assets/{terminal-Ciia0wh2.js → terminal-CR6t-Oey.js} +0 -0
- /package/client/dist/assets/{terminal-DY42QANg.js → terminal-CY_sPfdD.js} +0 -0
- /package/client/dist/assets/{terminal-C0xx0SjA.js → terminal-Cj4HzP3p.js} +0 -0
- /package/client/dist/assets/{terminal-BYtreaaF.js → terminal-Cz_Puz0q.js} +0 -0
- /package/client/dist/assets/{terminal-DoxtVdma.js → terminal-DFEH9XTQ.js} +0 -0
- /package/client/dist/assets/{tickets-D5MSAPe_.js → tickets-BFrVcXAI.js} +0 -0
- /package/client/dist/assets/{tickets-DBV3wgQZ.js → tickets-C3gG--yT.js} +0 -0
- /package/client/dist/assets/{tickets-r4-oNV0R.js → tickets-Cfhwbdxj.js} +0 -0
- /package/client/dist/assets/{tickets-RZ0LyeQe.js → tickets-DcggvP3k.js} +0 -0
- /package/client/dist/assets/{tickets-d1A6EOHa.js → tickets-EnP6m_Ao.js} +0 -0
- /package/client/dist/assets/{tickets-Q0_pONEh.js → tickets-IbYhlr-6.js} +0 -0
- /package/client/dist/assets/{tickets-CVJQ-vRm.js → tickets-ZS5EsDxn.js} +0 -0
- /package/client/dist/assets/{tickets-CG_mo-Bg.js → tickets-lJR7pWNI.js} +0 -0
- /package/client/dist/assets/{twig-DvsO-WjW.js → twig-DCcGd8FE.js} +0 -0
- /package/client/dist/assets/{typespec-Brkt3IAA.js → typespec-HTphp-Uh.js} +0 -0
- /package/client/dist/assets/{vb-r121Uzxt.js → vb-Cb_yUcVd.js} +0 -0
- /package/client/dist/assets/{wgsl-BRX8uYh4.js → wgsl-DrKU3YXj.js} +0 -0
|
@@ -134,6 +134,17 @@ function createMobileRouter(deps) {
|
|
|
134
134
|
return;
|
|
135
135
|
void forward(res, 'GET', `/api/projects/${encodeURIComponent(pid)}/jobs/${encodeURIComponent(jid)}`, '');
|
|
136
136
|
});
|
|
137
|
+
// rails-as-loops reads: the global Loop catalog (for the mobile loop picker)
|
|
138
|
+
// and per-project loop-run state (the running surface — a loop run has no jobId
|
|
139
|
+
// so it can't be tailed via /jobs/:id). 404s downstream when Loops are disabled.
|
|
140
|
+
v1.get('/loops', (_req, res) => { void forward(res, 'GET', '/api/loops', ''); });
|
|
141
|
+
v1.get('/loops/factory', (_req, res) => { void forward(res, 'GET', '/api/loops/factory', ''); });
|
|
142
|
+
v1.get('/projects/:pid/loop-runs/:id', (req, res) => {
|
|
143
|
+
const pid = seg(req.params.pid), rid = seg(req.params.id);
|
|
144
|
+
if (!validate(res, [pid, PID_RE], [rid, JOBID_RE]))
|
|
145
|
+
return;
|
|
146
|
+
void forward(res, 'GET', `/api/projects/${encodeURIComponent(pid)}/loop-runs/${encodeURIComponent(rid)}`, '');
|
|
147
|
+
});
|
|
137
148
|
v1.get('/projects/:pid/tickets/:tid/spending-summary', (req, res) => {
|
|
138
149
|
const pid = seg(req.params.pid), tid = seg(req.params.tid);
|
|
139
150
|
if (!validate(res, [pid, PID_RE], [tid, NUM_RE]))
|
|
@@ -183,6 +194,14 @@ function createMobileRouter(deps) {
|
|
|
183
194
|
narrowed.aiEngine = b.aiEngine;
|
|
184
195
|
if (typeof b.model === 'string')
|
|
185
196
|
narrowed.model = b.model; // ultracode model
|
|
197
|
+
// rails-as-loops: let a mobile client launch a chosen Loop (factory or custom).
|
|
198
|
+
// Additive — the frozen bare-`mode` path is untouched (a v1 client sends no loopId).
|
|
199
|
+
if (typeof b.loopId === 'string')
|
|
200
|
+
narrowed.loopId = b.loopId;
|
|
201
|
+
if (b.reasoning_effort === 'low' || b.reasoning_effort === 'medium' || b.reasoning_effort === 'high')
|
|
202
|
+
narrowed.reasoning_effort = b.reasoning_effort;
|
|
203
|
+
if (typeof b.interactive === 'boolean')
|
|
204
|
+
narrowed.interactive = b.interactive;
|
|
186
205
|
void forward(res, 'POST', `/api/projects/${encodeURIComponent(pid)}/rails/${encodeURIComponent(i)}/launch`, '', narrowed);
|
|
187
206
|
});
|
|
188
207
|
v1.put('/projects/:pid/rails/:i/engine', (req, res) => {
|
|
@@ -375,6 +394,9 @@ exports.MOBILE_ALLOWLIST = [
|
|
|
375
394
|
{ method: 'GET', path: '/v1/projects/:pid/tickets/:tid' },
|
|
376
395
|
{ method: 'GET', path: '/v1/projects/:pid/jobs' },
|
|
377
396
|
{ method: 'GET', path: '/v1/projects/:pid/jobs/:jid' },
|
|
397
|
+
{ method: 'GET', path: '/v1/loops' },
|
|
398
|
+
{ method: 'GET', path: '/v1/loops/factory' },
|
|
399
|
+
{ method: 'GET', path: '/v1/projects/:pid/loop-runs/:id' },
|
|
378
400
|
{ method: 'GET', path: '/v1/projects/:pid/queue' },
|
|
379
401
|
{ method: 'GET', path: '/v1/projects/:pid/rails' },
|
|
380
402
|
{ method: 'GET', path: '/v1/projects/:pid/activity' },
|
|
@@ -27,7 +27,15 @@ function topicFor(type) {
|
|
|
27
27
|
case 'rail.job_started':
|
|
28
28
|
case 'rail.job_stopped':
|
|
29
29
|
case 'rail.job_completed':
|
|
30
|
-
case 'rail.updated':
|
|
30
|
+
case 'rail.updated':
|
|
31
|
+
// rails-as-loops: loop runs (no queue job) report their lifecycle via the
|
|
32
|
+
// loop.run_* family. Map them onto the EXISTING 'rails' topic (frozen-safe —
|
|
33
|
+
// the companion's subscribe set is unchanged; an old v1 client ignores the
|
|
34
|
+
// unknown `loop.run_*` types harmlessly, a new client mirrors the run).
|
|
35
|
+
case 'loop.run_started':
|
|
36
|
+
case 'loop.run_progress':
|
|
37
|
+
case 'loop.run_stopped':
|
|
38
|
+
case 'loop.run_completed': return 'rails';
|
|
31
39
|
case 'spending.invalidated': return 'spending';
|
|
32
40
|
case 'chat_stream':
|
|
33
41
|
case 'chat_done':
|
|
@@ -59,6 +67,14 @@ const LEGACY_WIRE_TYPES = {
|
|
|
59
67
|
desktop_daily_budget_exceeded: 'hub_daily_budget_exceeded',
|
|
60
68
|
};
|
|
61
69
|
function toMobileWire(msg) {
|
|
70
|
+
// SECURITY: loop.run_progress carries `reasoning` — free-text LLM output that
|
|
71
|
+
// the path-only `redact()` does NOT scrub for repo content. Drop it at the
|
|
72
|
+
// mobile boundary (forward only iteration/activeNode); it's the one field that
|
|
73
|
+
// would leak more than any existing rail.* frame. Forward everything else.
|
|
74
|
+
if (msg.type === 'loop.run_progress') {
|
|
75
|
+
const { reasoning: _drop, ...rest } = msg;
|
|
76
|
+
return rest;
|
|
77
|
+
}
|
|
62
78
|
const legacyType = LEGACY_WIRE_TYPES[msg.type];
|
|
63
79
|
if (!legacyType)
|
|
64
80
|
return msg;
|
|
@@ -32,6 +32,9 @@ const workspace_resolution_1 = require("./workspace-resolution");
|
|
|
32
32
|
const workspace_manager_1 = require("./workspace-manager");
|
|
33
33
|
const ticket_store_1 = require("./ticket-store");
|
|
34
34
|
const jira_sync_manager_1 = require("./jira/jira-sync-manager");
|
|
35
|
+
const loop_run_manager_1 = require("./loop-run-manager");
|
|
36
|
+
const loop_executors_1 = require("./loop-executors");
|
|
37
|
+
const loop_runs_store_1 = require("./loop-runs-store");
|
|
35
38
|
const rails_store_1 = require("./rails-store");
|
|
36
39
|
const desktop_db_1 = require("./desktop-db");
|
|
37
40
|
const config_1 = require("./config");
|
|
@@ -626,7 +629,106 @@ class ProjectRegistry {
|
|
|
626
629
|
// projects instead of a per-project profile.
|
|
627
630
|
contextPool: this._browserContextPool,
|
|
628
631
|
});
|
|
629
|
-
|
|
632
|
+
// ── Loops engine (the Loops feature) ──────────────────────────────────────
|
|
633
|
+
const railLoopRuns = new Map();
|
|
634
|
+
// A restart kills any in-flight loop process, so reconcile orphan 'running'
|
|
635
|
+
// rows to terminal — otherwise they keep the loop un-editable (isRunning 409).
|
|
636
|
+
try {
|
|
637
|
+
const orphans = (0, loop_runs_store_1.reconcileOrphanLoopRuns)(db, new Date().toISOString());
|
|
638
|
+
if (orphans > 0)
|
|
639
|
+
console.log(`[loops] reconciled ${orphans} orphan loop run(s) for ${project.slug}`);
|
|
640
|
+
}
|
|
641
|
+
catch { /* non-fatal */ }
|
|
642
|
+
const loopRunManager = new loop_run_manager_1.LoopRunManager(db, boundBroadcast, (0, loop_executors_1.createLoopExecutors)());
|
|
643
|
+
const getTicketSpec = (ticketId) => {
|
|
644
|
+
try {
|
|
645
|
+
const exec = (0, workspace_resolution_1.resolveProjectExecution)({ slug: project.slug, path: project.path });
|
|
646
|
+
const ticketFile = exec.relocated ? exec.ticketsPath : (0, ticket_store_1.resolveTicketStoragePath)(project.path);
|
|
647
|
+
const t = (0, ticket_store_1.readStore)(ticketFile).tickets[String(ticketId)];
|
|
648
|
+
if (!t)
|
|
649
|
+
return undefined;
|
|
650
|
+
// Expose every field a loop prompt can reference via `{{spec.*}}`.
|
|
651
|
+
return {
|
|
652
|
+
id: t.id,
|
|
653
|
+
title: t.title,
|
|
654
|
+
description: t.description,
|
|
655
|
+
status: t.status,
|
|
656
|
+
priority: t.priority,
|
|
657
|
+
labels: t.labels,
|
|
658
|
+
jira_key: t.jira_key ?? null,
|
|
659
|
+
jira_url: t.jira_url ?? null,
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
catch {
|
|
663
|
+
return undefined;
|
|
664
|
+
}
|
|
665
|
+
};
|
|
666
|
+
// Releases a finished loop run's tickets + rail slots. The engine already
|
|
667
|
+
// emitted loop.run_completed; this mirrors onJobFinished's ticket/rail
|
|
668
|
+
// release (kept separate so the critical job path is untouched).
|
|
669
|
+
const onLoopRunFinished = (runId, outcome) => {
|
|
670
|
+
const meta = railLoopRuns.get(runId);
|
|
671
|
+
if (!meta)
|
|
672
|
+
return;
|
|
673
|
+
railLoopRuns.delete(runId);
|
|
674
|
+
const ticketIds = meta.ticketIds;
|
|
675
|
+
if (ticketIds.length === 0)
|
|
676
|
+
return;
|
|
677
|
+
const status = outcome === 'success' ? 'completed' : outcome === 'stopped' ? 'canceled' : 'failed';
|
|
678
|
+
try {
|
|
679
|
+
const exec = (0, workspace_resolution_1.resolveProjectExecution)({ slug: project.slug, path: project.path });
|
|
680
|
+
const ticketFile = exec.relocated ? exec.ticketsPath : (0, ticket_store_1.resolveTicketStoragePath)(project.path);
|
|
681
|
+
const now = new Date().toISOString();
|
|
682
|
+
let changedIds = [];
|
|
683
|
+
const store = (0, ticket_store_1.mutateStore)(ticketFile, (s) => {
|
|
684
|
+
changedIds = (0, ticket_store_1.applyJobOutcomeToTickets)(s, ticketIds, status, now);
|
|
685
|
+
});
|
|
686
|
+
for (const tid of changedIds) {
|
|
687
|
+
const ticket = store.tickets[String(tid)];
|
|
688
|
+
if (!ticket)
|
|
689
|
+
continue;
|
|
690
|
+
boundBroadcast({
|
|
691
|
+
type: 'ticket_updated',
|
|
692
|
+
ticket: ticket,
|
|
693
|
+
projectId: project.id,
|
|
694
|
+
timestamp: ticket.updated_at,
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
try {
|
|
698
|
+
const needsReviewIds = ticketIds.filter((tid) => store.tickets[String(tid)]?.needs_review === true);
|
|
699
|
+
jiraSyncManager.onJobOutcome({ ticketIds, status, jobId: runId, costUsd: null, durationMs: null, needsReviewIds });
|
|
700
|
+
}
|
|
701
|
+
catch (err) {
|
|
702
|
+
console.error('[project-registry] jira loop onJobOutcome failed:', err);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
catch (err) {
|
|
706
|
+
console.error('[project-registry] failed to apply loop outcome to tickets:', err);
|
|
707
|
+
}
|
|
708
|
+
try {
|
|
709
|
+
for (const rail of (0, rails_store_1.getRails)(db)) {
|
|
710
|
+
const remaining = rail.ticketIds.filter((id) => !ticketIds.includes(id));
|
|
711
|
+
if (remaining.length === rail.ticketIds.length)
|
|
712
|
+
continue;
|
|
713
|
+
(0, rails_store_1.setRailTickets)(db, rail.railIndex, remaining, rail.mode, rail.profileName, rail.aiEngine);
|
|
714
|
+
boundBroadcast({
|
|
715
|
+
type: 'rail.updated',
|
|
716
|
+
projectId: project.id,
|
|
717
|
+
railIndex: rail.railIndex,
|
|
718
|
+
changed: 'tickets',
|
|
719
|
+
ticketIds: remaining,
|
|
720
|
+
name: rail.name ?? null,
|
|
721
|
+
mode: rail.mode,
|
|
722
|
+
profileName: rail.profileName ?? null,
|
|
723
|
+
aiEngine: rail.aiEngine ?? null,
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
catch (err) {
|
|
728
|
+
console.error('[project-registry] failed to release rail tickets after loop run:', err);
|
|
729
|
+
}
|
|
730
|
+
};
|
|
731
|
+
const ctx = { project, db, queueManager, chatManager, setupManager, proposalManager, agentRefineManager, fileSummaryManager, specLauncherManager, ticketWatcher, browserCaptureManager, jiraSyncManager, broadcast: boundBroadcast, railJobs, loopRunManager, railLoopRuns, onLoopRunFinished, getTicketSpec, desktopDb: this._desktopDb };
|
|
630
732
|
this._contexts.set(project.id, ctx);
|
|
631
733
|
return ctx;
|
|
632
734
|
}
|
|
@@ -101,8 +101,18 @@ function registerJobsRoutes(deps) {
|
|
|
101
101
|
res.json({ model, provider, allowed, providers: project.providers });
|
|
102
102
|
});
|
|
103
103
|
router.delete('/:projectId/jobs/:id', (req, res) => {
|
|
104
|
+
const id = req.params.id;
|
|
105
|
+
const c = ctx(req);
|
|
106
|
+
// A loop run is backed by a job row but is NOT a QueueManager job — it is
|
|
107
|
+
// driven by the LoopRunManager. Cancel it through the engine (which settles
|
|
108
|
+
// it 'stopped' → finishJob('canceled') + releases the rail's tickets).
|
|
109
|
+
if (c.railLoopRuns?.has(id)) {
|
|
110
|
+
c.loopRunManager?.cancel(id);
|
|
111
|
+
res.json({ ok: true, status: 'canceling' });
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
104
114
|
try {
|
|
105
|
-
const result =
|
|
115
|
+
const result = c.queueManager.cancel(id);
|
|
106
116
|
res.json({ ok: true, status: result });
|
|
107
117
|
}
|
|
108
118
|
catch (err) {
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerLoopRunRoutes = registerLoopRunRoutes;
|
|
4
|
+
const feature_flags_1 = require("./feature-flags");
|
|
5
|
+
const loops_store_1 = require("./loops-store");
|
|
6
|
+
const loop_runs_store_1 = require("./loop-runs-store");
|
|
7
|
+
const loop_constants_1 = require("./loop-constants");
|
|
8
|
+
const providers_1 = require("./providers");
|
|
9
|
+
const provider_selection_1 = require("./provider-selection");
|
|
10
|
+
const workspace_resolution_1 = require("./workspace-resolution");
|
|
11
|
+
const loop_command_catalog_1 = require("./loop-command-catalog");
|
|
12
|
+
const ids_1 = require("./ids");
|
|
13
|
+
const VALID_REASONING_EFFORTS = new Set(['low', 'medium', 'high']);
|
|
14
|
+
function registerLoopRunRoutes(deps) {
|
|
15
|
+
const { router, ctx } = deps;
|
|
16
|
+
// GET a single loop run's live/terminal state. Backs the companion's running
|
|
17
|
+
// surface (a loop run has no jobId, so it can't be tailed via /jobs/:id).
|
|
18
|
+
router.get('/:projectId/loop-runs/:id', (req, res) => {
|
|
19
|
+
if (!(0, feature_flags_1.isLoopsEnabled)()) {
|
|
20
|
+
res.status(404).json({ error: 'Not Found' });
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const c = ctx(req);
|
|
24
|
+
const run = (0, loop_runs_store_1.getLoopRun)(c.db, req.params.id);
|
|
25
|
+
if (!run || run.project_id !== c.project.id) {
|
|
26
|
+
res.status(404).json({ error: 'Loop run not found' });
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
res.json({ loopRun: run });
|
|
30
|
+
});
|
|
31
|
+
router.post('/:projectId/loop-runs', (req, res) => {
|
|
32
|
+
if (!(0, feature_flags_1.isLoopsEnabled)()) {
|
|
33
|
+
res.status(404).json({ error: 'Not Found' });
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const c = ctx(req);
|
|
37
|
+
const body = req.body ?? {};
|
|
38
|
+
const { loopId, aiEngine, provider: providerAlias, reasoning_effort } = body;
|
|
39
|
+
if (typeof loopId !== 'string' || !loopId) {
|
|
40
|
+
res.status(400).json({ error: 'loopId is required' });
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const loop = (0, loops_store_1.getLoop)(c.desktopDb, loopId);
|
|
44
|
+
if (!loop) {
|
|
45
|
+
res.status(404).json({ error: 'Loop not found' });
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (loop.status !== 'published') {
|
|
49
|
+
res.status(400).json({ error: 'Loop must be published before it can run' });
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
let effort;
|
|
53
|
+
if (reasoning_effort !== undefined && reasoning_effort !== null) {
|
|
54
|
+
if (typeof reasoning_effort !== 'string' || !VALID_REASONING_EFFORTS.has(reasoning_effort)) {
|
|
55
|
+
res.status(400).json({ error: 'reasoning_effort must be one of: low, medium, high' });
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
effort = reasoning_effort;
|
|
59
|
+
}
|
|
60
|
+
const check = (0, provider_selection_1.validateRequestedProvider)(c.project, aiEngine ?? providerAlias);
|
|
61
|
+
if (!check.ok) {
|
|
62
|
+
res.status(400).json({ error: check.error });
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const provider = check.provider;
|
|
66
|
+
// claude-only guard (e.g. a loop that uses {{cmd:ultracode}}).
|
|
67
|
+
const promptsText = loop.graph.nodes
|
|
68
|
+
.filter((n) => n.type === 'ai-step')
|
|
69
|
+
.map((n) => String(n.data?.prompt ?? ''))
|
|
70
|
+
.join('\n');
|
|
71
|
+
if ((0, loop_command_catalog_1.referencesClaudeOnlyCommand)(promptsText) && provider !== 'claude') {
|
|
72
|
+
res.status(400).json({ error: 'This loop uses a Claude-only command and requires the Claude provider' });
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const model = (0, providers_1.getAdapter)(provider).defaultModel();
|
|
76
|
+
const exec = (0, workspace_resolution_1.resolveProjectExecution)({ slug: c.project.slug, path: c.project.path });
|
|
77
|
+
const runId = (0, ids_1.newId)();
|
|
78
|
+
c.loopRunManager
|
|
79
|
+
.run({
|
|
80
|
+
runId,
|
|
81
|
+
loopId,
|
|
82
|
+
loopName: loop.name,
|
|
83
|
+
graph: loop.graph,
|
|
84
|
+
projectId: c.project.id,
|
|
85
|
+
cwd: exec.cwd,
|
|
86
|
+
repoDir: exec.relocated ? exec.repoDir : undefined,
|
|
87
|
+
railIndex: null,
|
|
88
|
+
ticketId: null,
|
|
89
|
+
spec: undefined,
|
|
90
|
+
constants: (0, loop_constants_1.loadConstantMap)(c.desktopDb),
|
|
91
|
+
provider,
|
|
92
|
+
model,
|
|
93
|
+
effort,
|
|
94
|
+
})
|
|
95
|
+
.then((r) => c.onLoopRunFinished(r.runId, r.outcome))
|
|
96
|
+
.catch((err) => {
|
|
97
|
+
console.error('[loop-runs] standalone run failed:', err);
|
|
98
|
+
c.onLoopRunFinished(runId, 'failed');
|
|
99
|
+
});
|
|
100
|
+
res.status(202).json({ loopRunId: runId });
|
|
101
|
+
});
|
|
102
|
+
}
|
|
@@ -18,6 +18,7 @@ const project_router_setup_1 = require("./project-router-setup");
|
|
|
18
18
|
const project_router_tickets_1 = require("./project-router-tickets");
|
|
19
19
|
const project_router_terminals_1 = require("./project-router-terminals");
|
|
20
20
|
const project_router_settings_1 = require("./project-router-settings");
|
|
21
|
+
const project_router_loop_runs_1 = require("./project-router-loop-runs");
|
|
21
22
|
// Re-export the spec helpers from their new home so existing importers
|
|
22
23
|
// (`import { ... } from './project-router'`) keep working unchanged.
|
|
23
24
|
var project_router_helpers_1 = require("./project-router-helpers");
|
|
@@ -115,5 +116,6 @@ function createProjectRouter(registry) {
|
|
|
115
116
|
(0, project_router_tickets_1.registerTicketsRoutes)(deps);
|
|
116
117
|
(0, project_router_terminals_1.registerTerminalsRoutes)(deps);
|
|
117
118
|
(0, project_router_settings_1.registerSettingsRoutes)(deps);
|
|
119
|
+
(0, project_router_loop_runs_1.registerLoopRunRoutes)(deps);
|
|
118
120
|
return router;
|
|
119
121
|
}
|
|
@@ -68,6 +68,9 @@ function commonFlagsFor(opts) {
|
|
|
68
68
|
...COMMON_FLAGS,
|
|
69
69
|
'--setting-sources',
|
|
70
70
|
opts.loadUserEnv ? 'user,project,local' : 'project,local',
|
|
71
|
+
// Native reasoning-effort flag (claude CLI accepts low|medium|high|xhigh|max).
|
|
72
|
+
// Emitted only when requested; absent ⇒ the CLI's default effort.
|
|
73
|
+
...(opts.reasoning_effort ? ['--effort', opts.reasoning_effort] : []),
|
|
71
74
|
];
|
|
72
75
|
}
|
|
73
76
|
function buildClaudeArgs(action, opts) {
|
|
@@ -313,6 +316,7 @@ exports.claudeAdapter = {
|
|
|
313
316
|
profileEnvSupport: true,
|
|
314
317
|
systemPromptArg: true,
|
|
315
318
|
persistentStdin: true,
|
|
319
|
+
supportsReasoningEffort: true,
|
|
316
320
|
},
|
|
317
321
|
modelCatalog: () => CLAUDE_MODELS,
|
|
318
322
|
defaultModel: () => 'sonnet',
|
|
@@ -66,6 +66,10 @@ function buildCodexArgs(action, opts) {
|
|
|
66
66
|
args.push('exec', '--json', ...SANDBOX_FLAGS, SKIP_GIT_CHECK);
|
|
67
67
|
args.push(opts.prompt);
|
|
68
68
|
args.push('--model', opts.model);
|
|
69
|
+
// Native reasoning effort (codex 0.139+). String value is quoted to match
|
|
70
|
+
// the `-c key="value"` override form used elsewhere in this adapter.
|
|
71
|
+
if (opts.reasoning_effort)
|
|
72
|
+
args.push('-c', `model_reasoning_effort="${opts.reasoning_effort}"`);
|
|
69
73
|
if (opts.extraArgs)
|
|
70
74
|
args.push(...opts.extraArgs);
|
|
71
75
|
return args;
|
|
@@ -77,6 +81,10 @@ function buildCodexArgs(action, opts) {
|
|
|
77
81
|
args.push('exec', '--json', ...SANDBOX_FLAGS, SKIP_GIT_CHECK);
|
|
78
82
|
args.push(fold(opts.systemPrompt, opts.prompt));
|
|
79
83
|
args.push('--model', opts.model);
|
|
84
|
+
// Native reasoning effort (codex 0.139+). String value is quoted to match
|
|
85
|
+
// the `-c key="value"` override form used elsewhere in this adapter.
|
|
86
|
+
if (opts.reasoning_effort)
|
|
87
|
+
args.push('-c', `model_reasoning_effort="${opts.reasoning_effort}"`);
|
|
80
88
|
if (opts.extraArgs)
|
|
81
89
|
args.push(...opts.extraArgs);
|
|
82
90
|
return args;
|
|
@@ -92,6 +100,10 @@ function buildCodexArgs(action, opts) {
|
|
|
92
100
|
args.push(opts.sessionId);
|
|
93
101
|
args.push(opts.prompt);
|
|
94
102
|
args.push('--model', opts.model);
|
|
103
|
+
// Native reasoning effort (codex 0.139+). String value is quoted to match
|
|
104
|
+
// the `-c key="value"` override form used elsewhere in this adapter.
|
|
105
|
+
if (opts.reasoning_effort)
|
|
106
|
+
args.push('-c', `model_reasoning_effort="${opts.reasoning_effort}"`);
|
|
95
107
|
if (opts.extraArgs)
|
|
96
108
|
args.push(...opts.extraArgs);
|
|
97
109
|
return args;
|
|
@@ -104,6 +116,10 @@ function buildCodexArgs(action, opts) {
|
|
|
104
116
|
args.push(opts.sessionId);
|
|
105
117
|
args.push(fold(opts.systemPrompt, opts.prompt));
|
|
106
118
|
args.push('--model', opts.model);
|
|
119
|
+
// Native reasoning effort (codex 0.139+). String value is quoted to match
|
|
120
|
+
// the `-c key="value"` override form used elsewhere in this adapter.
|
|
121
|
+
if (opts.reasoning_effort)
|
|
122
|
+
args.push('-c', `model_reasoning_effort="${opts.reasoning_effort}"`);
|
|
107
123
|
if (opts.extraArgs)
|
|
108
124
|
args.push(...opts.extraArgs);
|
|
109
125
|
return args;
|
|
@@ -123,6 +139,10 @@ function buildCodexArgs(action, opts) {
|
|
|
123
139
|
args.push('exec', '--json', ...RAIL_SANDBOX_FLAGS, SKIP_GIT_CHECK);
|
|
124
140
|
args.push(fold(opts.systemPrompt, opts.prompt));
|
|
125
141
|
args.push('--model', opts.model);
|
|
142
|
+
// Native reasoning effort (codex 0.139+). String value is quoted to match
|
|
143
|
+
// the `-c key="value"` override form used elsewhere in this adapter.
|
|
144
|
+
if (opts.reasoning_effort)
|
|
145
|
+
args.push('-c', `model_reasoning_effort="${opts.reasoning_effort}"`);
|
|
126
146
|
if (opts.extraArgs)
|
|
127
147
|
args.push(...opts.extraArgs);
|
|
128
148
|
return args;
|
|
@@ -288,6 +308,7 @@ exports.codexAdapter = {
|
|
|
288
308
|
nativeOtelEnv: false,
|
|
289
309
|
profileEnvSupport: true,
|
|
290
310
|
systemPromptArg: false,
|
|
311
|
+
supportsReasoningEffort: true,
|
|
291
312
|
},
|
|
292
313
|
modelCatalog: () => CODEX_MODELS,
|
|
293
314
|
defaultModel: () => 'gpt-5.5',
|
|
@@ -229,6 +229,10 @@ exports.geminiAdapter = {
|
|
|
229
229
|
nativeOtelEnv: true,
|
|
230
230
|
profileEnvSupport: true,
|
|
231
231
|
systemPromptArg: false,
|
|
232
|
+
// gemini-cli has NO per-invocation reasoning-effort flag (thinking levels
|
|
233
|
+
// require settings.json `thinkingConfig` + custom model aliases, not a
|
|
234
|
+
// per-spawn arg), so effort is not offered and the adapter ignores it.
|
|
235
|
+
supportsReasoningEffort: false,
|
|
232
236
|
},
|
|
233
237
|
modelCatalog: () => GEMINI_MODELS,
|
|
234
238
|
defaultModel: () => 'gemini-3.5-flash',
|
|
@@ -6,10 +6,21 @@ const rails_store_1 = require("./rails-store");
|
|
|
6
6
|
const queue_manager_1 = require("./queue-manager");
|
|
7
7
|
const provider_selection_1 = require("./provider-selection");
|
|
8
8
|
const feature_flags_1 = require("./feature-flags");
|
|
9
|
-
const
|
|
9
|
+
const loops_store_1 = require("./loops-store");
|
|
10
|
+
const loop_runs_store_1 = require("./loop-runs-store");
|
|
11
|
+
const providers_1 = require("./providers");
|
|
12
|
+
const workspace_resolution_1 = require("./workspace-resolution");
|
|
13
|
+
const loop_factory_1 = require("./loop-factory");
|
|
14
|
+
const loop_constants_1 = require("./loop-constants");
|
|
15
|
+
const loop_command_catalog_1 = require("./loop-command-catalog");
|
|
16
|
+
const ids_1 = require("./ids");
|
|
17
|
+
const VALID_MODES = new Set(['implement', 'batch-implement', 'ultracode', 'loop']);
|
|
10
18
|
// Models the ultracode picker exposes (Claude aliases). Mirrors the client
|
|
11
19
|
// RailModelSelector options and the project-router orchestrator-model allow-list.
|
|
12
20
|
const VALID_ULTRACODE_MODELS = new Set(['haiku', 'sonnet', 'opus']);
|
|
21
|
+
// Reasoning-effort tiers a loop launch may request (mirrors the client selector
|
|
22
|
+
// + the provider adapters' supported values).
|
|
23
|
+
const VALID_REASONING_EFFORTS = new Set(['low', 'medium', 'high']);
|
|
13
24
|
function createRailsRouter() {
|
|
14
25
|
const router = (0, express_1.Router)({ mergeParams: true });
|
|
15
26
|
function ctx(req) {
|
|
@@ -46,7 +57,19 @@ function createRailsRouter() {
|
|
|
46
57
|
for (const [jobId, meta] of c.railJobs.entries()) {
|
|
47
58
|
activeJobs[meta.railIndex] = { jobId, mode: meta.mode };
|
|
48
59
|
}
|
|
49
|
-
|
|
60
|
+
// Active loop runs (Loops mode) so clients can reconcile a 'running' rail
|
|
61
|
+
// that's executing a loop rather than a queue job. Enriched from the
|
|
62
|
+
// loop_runs row with the loop identity + resolved provider/model so a
|
|
63
|
+
// mirror (e.g. the mobile companion) can label the run instead of showing a
|
|
64
|
+
// stale `mode`/null engine — these never land on the rail row itself.
|
|
65
|
+
const activeLoopRuns = {};
|
|
66
|
+
for (const [runId, meta] of c.railLoopRuns.entries()) {
|
|
67
|
+
const run = (0, loop_runs_store_1.getLoopRun)(c.db, runId);
|
|
68
|
+
activeLoopRuns[meta.railIndex] = run
|
|
69
|
+
? { loopRunId: runId, loopId: run.loop_id, loopName: run.loop_name, provider: run.provider, model: run.model, iteration: run.iteration_count }
|
|
70
|
+
: { loopRunId: runId };
|
|
71
|
+
}
|
|
72
|
+
res.json({ rails, activeJobs, activeLoopRuns });
|
|
50
73
|
}
|
|
51
74
|
catch (err) {
|
|
52
75
|
console.error('[rails-router] get rails error:', err);
|
|
@@ -190,9 +213,26 @@ function createRailsRouter() {
|
|
|
190
213
|
res.status(400).json({ error: 'Invalid rail index' });
|
|
191
214
|
return;
|
|
192
215
|
}
|
|
193
|
-
|
|
216
|
+
let { mode = 'implement' } = req.body ?? {};
|
|
217
|
+
const { profileName, aiEngine, model, interactive, loopId, reasoning_effort } = req.body ?? {};
|
|
218
|
+
// rails-as-loops: a FACTORY loop id (`factory:implement` etc.) maps to its
|
|
219
|
+
// legacy mode and runs through the EXISTING engine (QueueManager) — the rail
|
|
220
|
+
// "picks a Loop", the plumbing is unchanged. A CUSTOM loop id keeps mode='loop'
|
|
221
|
+
// (LoopRunManager). Legacy callers that still send a bare `mode` are unchanged.
|
|
222
|
+
if (typeof loopId === 'string' && (0, loop_factory_1.isFactoryLoopId)(loopId)) {
|
|
223
|
+
const fmode = (0, loop_factory_1.factoryLoopMode)(loopId);
|
|
224
|
+
if (!fmode) {
|
|
225
|
+
res.status(404).json({ error: 'Factory loop not found' });
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
mode = fmode;
|
|
229
|
+
}
|
|
194
230
|
if (!VALID_MODES.has(mode)) {
|
|
195
|
-
res.status(400).json({ error: 'mode must be "implement", "batch-implement" or "
|
|
231
|
+
res.status(400).json({ error: 'mode must be "implement", "batch-implement", "ultracode" or "loop"' });
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
if (mode === 'loop' && !(0, feature_flags_1.isLoopsEnabled)()) {
|
|
235
|
+
res.status(403).json({ error: 'Loops are disabled on this server' });
|
|
196
236
|
return;
|
|
197
237
|
}
|
|
198
238
|
// Ultracode model picker: optional, validated against the allow-list.
|
|
@@ -262,6 +302,113 @@ function createRailsRouter() {
|
|
|
262
302
|
resolvedProfile = undefined; // fall through to QueueManager default resolution
|
|
263
303
|
}
|
|
264
304
|
try {
|
|
305
|
+
// Loop mode: run a published loop (the Loops feature) against each spec,
|
|
306
|
+
// one app-driven LoopRun per ticket (mirrors ultracode's per-ticket model).
|
|
307
|
+
// Drives raw prompts via the LoopRunManager — NO queue job, NO slash command.
|
|
308
|
+
// rails-as-loops: a chosen loop (factory OR custom) runs through the
|
|
309
|
+
// LoopRunManager when Loops are enabled — so factory loops get their
|
|
310
|
+
// autonomous verify→fix loop too. (Loops off / no loopId → the legacy
|
|
311
|
+
// bare-mode QueueManager path below; `mode` is the derived factory mode.)
|
|
312
|
+
if ((0, feature_flags_1.isLoopsEnabled)() && typeof loopId === 'string' && loopId) {
|
|
313
|
+
let loopGraph;
|
|
314
|
+
let loopName;
|
|
315
|
+
if ((0, loop_factory_1.isFactoryLoopId)(loopId)) {
|
|
316
|
+
const f = (0, loop_factory_1.getFactoryLoop)(loopId);
|
|
317
|
+
if (!f) {
|
|
318
|
+
res.status(404).json({ error: 'Factory loop not found' });
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
loopGraph = f.graph;
|
|
322
|
+
loopName = f.name;
|
|
323
|
+
}
|
|
324
|
+
else {
|
|
325
|
+
const loop = (0, loops_store_1.getLoop)(c.desktopDb, loopId);
|
|
326
|
+
if (!loop) {
|
|
327
|
+
res.status(404).json({ error: 'Loop not found' });
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
if (loop.status !== 'published') {
|
|
331
|
+
res.status(400).json({ error: 'Loop must be published before it can run on a rail' });
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
loopGraph = loop.graph;
|
|
335
|
+
loopName = loop.name;
|
|
336
|
+
}
|
|
337
|
+
let effort;
|
|
338
|
+
if (reasoning_effort !== undefined && reasoning_effort !== null) {
|
|
339
|
+
if (typeof reasoning_effort !== 'string' || !VALID_REASONING_EFFORTS.has(reasoning_effort)) {
|
|
340
|
+
res.status(400).json({ error: 'reasoning_effort must be one of: low, medium, high' });
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
effort = reasoning_effort;
|
|
344
|
+
}
|
|
345
|
+
const loopProvider = railProvider ?? c.project.provider ?? 'claude';
|
|
346
|
+
const loopModel = typeof model === 'string' && model ? model : (0, providers_1.getAdapter)(loopProvider).defaultModel();
|
|
347
|
+
// The loop's command(s) declare ticket scope + claude-only-ness.
|
|
348
|
+
const promptsText = loopGraph.nodes
|
|
349
|
+
.filter((n) => n.type === 'ai-step')
|
|
350
|
+
.map((n) => String(n.data?.prompt ?? ''))
|
|
351
|
+
.join('\n');
|
|
352
|
+
if ((0, loop_command_catalog_1.referencesClaudeOnlyCommand)(promptsText) && loopProvider !== 'claude') {
|
|
353
|
+
res.status(400).json({ error: 'This loop uses a Claude-only command and requires the Claude provider' });
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
const scope = (0, loop_command_catalog_1.dominantTicketScope)(promptsText);
|
|
357
|
+
// Spawn from the SAME cwd a rail uses (workspace when relocated, else the
|
|
358
|
+
// repo) so native `{{cmd:*}}` slash commands resolve — and surface the repo
|
|
359
|
+
// via SPECRAILS_REPO_DIR + `--add-dir` exactly like QueueManager.
|
|
360
|
+
const loopExec = (0, workspace_resolution_1.resolveProjectExecution)({ slug: c.project.slug, path: c.project.path });
|
|
361
|
+
const loopRunIds = [];
|
|
362
|
+
const launchLoopRun = (runId, ticketIds, spec) => {
|
|
363
|
+
c.railLoopRuns.set(runId, { railIndex, ticketIds });
|
|
364
|
+
c.loopRunManager
|
|
365
|
+
.run({
|
|
366
|
+
runId,
|
|
367
|
+
loopId,
|
|
368
|
+
loopName,
|
|
369
|
+
graph: loopGraph,
|
|
370
|
+
projectId: c.project.id,
|
|
371
|
+
cwd: loopExec.cwd,
|
|
372
|
+
repoDir: loopExec.relocated ? loopExec.repoDir : undefined,
|
|
373
|
+
railIndex,
|
|
374
|
+
ticketId: ticketIds[0],
|
|
375
|
+
spec: spec ? { ...spec, ticketIds } : { ticketIds },
|
|
376
|
+
constants: (0, loop_constants_1.loadConstantMap)(c.desktopDb),
|
|
377
|
+
provider: loopProvider,
|
|
378
|
+
model: loopModel,
|
|
379
|
+
effort,
|
|
380
|
+
})
|
|
381
|
+
.then((r) => c.onLoopRunFinished(r.runId, r.outcome))
|
|
382
|
+
.catch((err) => {
|
|
383
|
+
console.error('[rails-router] loop run failed:', err);
|
|
384
|
+
c.onLoopRunFinished(runId, 'failed');
|
|
385
|
+
});
|
|
386
|
+
loopRunIds.push(runId);
|
|
387
|
+
try {
|
|
388
|
+
c.jiraSyncManager.onRailLaunch(ticketIds, runId);
|
|
389
|
+
}
|
|
390
|
+
catch { /* non-fatal */ }
|
|
391
|
+
};
|
|
392
|
+
if (scope === 'all') {
|
|
393
|
+
// ONE run over ALL the rail's tickets ({{spec.ids}} = #1 #2 #3).
|
|
394
|
+
const allIds = [...rail.ticketIds];
|
|
395
|
+
launchLoopRun((0, ids_1.newId)(), allIds, c.getTicketSpec(allIds[0]));
|
|
396
|
+
}
|
|
397
|
+
else {
|
|
398
|
+
// One run per ticket.
|
|
399
|
+
for (const ticketId of rail.ticketIds) {
|
|
400
|
+
launchLoopRun((0, ids_1.newId)(), [ticketId], c.getTicketSpec(ticketId));
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
res.status(202).json({ loopRunIds, railIndex, mode });
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
// A custom-loop launch (mode='loop') with no loopId is invalid — there is
|
|
407
|
+
// no factory loop to fall back to (only implement/batch/ultracode do).
|
|
408
|
+
if (mode === 'loop') {
|
|
409
|
+
res.status(400).json({ error: 'loopId is required for loop mode' });
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
265
412
|
let jobId;
|
|
266
413
|
if (mode === 'ultracode') {
|
|
267
414
|
// Ultracode launches ONE independent Claude job per ticket — each gets
|
|
@@ -355,7 +502,10 @@ function createRailsRouter() {
|
|
|
355
502
|
const targetJobIds = Array.from(c.railJobs.entries())
|
|
356
503
|
.filter(([, meta]) => meta.railIndex === railIndex)
|
|
357
504
|
.map(([jobId]) => jobId);
|
|
358
|
-
|
|
505
|
+
const targetLoopRunIds = Array.from(c.railLoopRuns.entries())
|
|
506
|
+
.filter(([, meta]) => meta.railIndex === railIndex)
|
|
507
|
+
.map(([runId]) => runId);
|
|
508
|
+
if (targetJobIds.length === 0 && targetLoopRunIds.length === 0) {
|
|
359
509
|
res.status(404).json({ error: 'No active rail job found for this rail' });
|
|
360
510
|
return;
|
|
361
511
|
}
|
|
@@ -382,7 +532,26 @@ function createRailsRouter() {
|
|
|
382
532
|
};
|
|
383
533
|
c.broadcast(stopMsg);
|
|
384
534
|
}
|
|
385
|
-
|
|
535
|
+
// Loop runs (Loops mode): request cancellation. The engine settles 'stopped'
|
|
536
|
+
// at the next node boundary and onLoopRunFinished releases the rail's tickets
|
|
537
|
+
// (so we deliberately do NOT delete railLoopRuns here).
|
|
538
|
+
for (const runId of targetLoopRunIds) {
|
|
539
|
+
try {
|
|
540
|
+
c.loopRunManager.cancel(runId);
|
|
541
|
+
canceledCount++;
|
|
542
|
+
}
|
|
543
|
+
catch (err) {
|
|
544
|
+
console.warn(`[rails-router] stop: loop cancel(${runId}) failed: ${err.message}`);
|
|
545
|
+
}
|
|
546
|
+
const stopMsg = {
|
|
547
|
+
type: 'loop.run_stopped',
|
|
548
|
+
projectId: c.project.id,
|
|
549
|
+
loopRunId: runId,
|
|
550
|
+
railIndex,
|
|
551
|
+
};
|
|
552
|
+
c.broadcast(stopMsg);
|
|
553
|
+
}
|
|
554
|
+
res.json({ ok: true, jobIds: targetJobIds, loopRunIds: targetLoopRunIds, canceled: canceledCount });
|
|
386
555
|
});
|
|
387
556
|
return router;
|
|
388
557
|
}
|