specrails-desktop 2.0.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/.claude/commands/specrails/batch-implement.md +287 -0
- package/.claude/commands/specrails/compat-check.md +271 -0
- package/.claude/commands/specrails/doctor.md +62 -0
- package/.claude/commands/specrails/enrich.md +1635 -0
- package/.claude/commands/specrails/explore-spec.md +173 -0
- package/.claude/commands/specrails/health-check.md +527 -0
- package/.claude/commands/specrails/implement.md +1457 -0
- package/.claude/commands/specrails/memory-inspect.md +259 -0
- package/.claude/commands/specrails/opsx-diff.md +419 -0
- package/.claude/commands/specrails/propose-spec.md +102 -0
- package/.claude/commands/specrails/reconfig.md +89 -0
- package/.claude/commands/specrails/refactor-recommender.md +212 -0
- package/.claude/commands/specrails/retry.md +363 -0
- package/.claude/commands/specrails/telemetry.md +552 -0
- package/.claude/commands/specrails/why.md +96 -0
- package/LICENSE +21 -0
- package/README.md +290 -0
- package/cli/dist/specrails-desktop.js +1098 -0
- package/client/dist/assets/ActivityFeedPage-Gy4x8dBt.js +1 -0
- package/client/dist/assets/AgentsPage-CPgu--Fb.js +86 -0
- package/client/dist/assets/AnalyticsPage-B5sJEee2.js +1 -0
- package/client/dist/assets/BarChart-7IMQ8HY1.js +33 -0
- package/client/dist/assets/CodePage-CBdFvbwe.js +2 -0
- package/client/dist/assets/DesktopAnalyticsPage-w0rdTq4w.js +1 -0
- package/client/dist/assets/DocsDialog-BZUYM7wm.js +11 -0
- package/client/dist/assets/DocsPage-9QglWl46.js +11 -0
- package/client/dist/assets/ExportDropdown-BLZFXtNi.js +1 -0
- package/client/dist/assets/IntegrationsPage-BxBE4y99.js +3 -0
- package/client/dist/assets/JobDetailPage-DydWx_5S.js +16 -0
- package/client/dist/assets/JobsPage-20ibw0IO.js +1 -0
- package/client/dist/assets/abap-Bw6f2wDG.js +1 -0
- package/client/dist/assets/activity-BEIp_Y1A.js +1 -0
- package/client/dist/assets/activity-BdrPln96.js +1 -0
- package/client/dist/assets/activity-CpkRS8Sx.js +1 -0
- package/client/dist/assets/activity-DKCpESPt.js +1 -0
- package/client/dist/assets/activity-DOUVEjJi.js +1 -0
- package/client/dist/assets/activity-DRwkql_y.js +1 -0
- package/client/dist/assets/activity-DcDQ7tjw.js +1 -0
- package/client/dist/assets/activity-Dv6H7wEr.js +1 -0
- package/client/dist/assets/addon-image-3WCl5Vhd.js +1 -0
- package/client/dist/assets/addon-ligatures-C5OdliKs.js +2 -0
- package/client/dist/assets/addon-webgl-BbX6pSjl.js +44 -0
- package/client/dist/assets/addspec-B5yl4Loj.js +1 -0
- package/client/dist/assets/addspec-BEeF5-zc.js +1 -0
- package/client/dist/assets/addspec-D33ocMxf.js +1 -0
- package/client/dist/assets/addspec-DFswZ0jK.js +1 -0
- package/client/dist/assets/addspec-DRE-jZv7.js +1 -0
- package/client/dist/assets/addspec-DVZ15Jp8.js +1 -0
- package/client/dist/assets/addspec-Fkv91Opc.js +1 -0
- package/client/dist/assets/addspec-GWm4ffKl.js +1 -0
- package/client/dist/assets/agents-1nCDWRmP.js +1 -0
- package/client/dist/assets/agents-Bm9rPqnt.js +1 -0
- package/client/dist/assets/agents-CMxtJMLD.js +1 -0
- package/client/dist/assets/agents-DK-Dlc0i.js +1 -0
- package/client/dist/assets/agents-Q6Ldfpxx.js +1 -0
- package/client/dist/assets/agents-TeOSy-ax.js +1 -0
- package/client/dist/assets/agents-iTqjRajS.js +1 -0
- package/client/dist/assets/agents-s87sMGzL.js +1 -0
- package/client/dist/assets/agentstudio-B6Wb59E7.js +1 -0
- package/client/dist/assets/agentstudio-BADhZ41e.js +1 -0
- package/client/dist/assets/agentstudio-BSnWLR63.js +1 -0
- package/client/dist/assets/agentstudio-BdidyBzZ.js +1 -0
- package/client/dist/assets/agentstudio-CxlUllqI.js +1 -0
- package/client/dist/assets/agentstudio-D3I62TLJ.js +1 -0
- package/client/dist/assets/agentstudio-DuH9TogZ.js +1 -0
- package/client/dist/assets/agentstudio-Kw88_dUF.js +1 -0
- package/client/dist/assets/aiedit-BWxHGsYA.js +1 -0
- package/client/dist/assets/aiedit-D2ji6Qy0.js +1 -0
- package/client/dist/assets/aiedit-DAhZTvtk.js +1 -0
- package/client/dist/assets/aiedit-DJMny-D5.js +1 -0
- package/client/dist/assets/aiedit-DOcxERkU.js +1 -0
- package/client/dist/assets/aiedit-DvrcbwGv.js +1 -0
- package/client/dist/assets/aiedit-TTwzL1TS.js +1 -0
- package/client/dist/assets/aiedit-WBSjT_C1.js +1 -0
- package/client/dist/assets/analytics-BIdr0YfL.js +1 -0
- package/client/dist/assets/analytics-C6EzgtdE.js +1 -0
- package/client/dist/assets/analytics-C9Zc-rkM.js +1 -0
- package/client/dist/assets/analytics-CVx3YOc0.js +1 -0
- package/client/dist/assets/analytics-CYj0tfj7.js +1 -0
- package/client/dist/assets/analytics-CnY4kNG3.js +1 -0
- package/client/dist/assets/analytics-CrPCZRJ-.js +1 -0
- package/client/dist/assets/analytics-DMCto-TF.js +1 -0
- package/client/dist/assets/apex-Cw8_REBo.js +1 -0
- package/client/dist/assets/atom-one-dark-B-oHczHB.css +1 -0
- package/client/dist/assets/attachments-BIsSSnHJ.js +1 -0
- package/client/dist/assets/attachments-BW4L3l2L.js +1 -0
- package/client/dist/assets/attachments-Bcf6BG6V.js +1 -0
- package/client/dist/assets/attachments-Bke8sCU4.js +1 -0
- package/client/dist/assets/attachments-COcrGRFz.js +1 -0
- package/client/dist/assets/attachments-DYHGA2Dj.js +1 -0
- package/client/dist/assets/attachments-Dd92KpUH.js +1 -0
- package/client/dist/assets/attachments-DzdU6DV6.js +1 -0
- package/client/dist/assets/azcli-Cz6HAoOw.js +1 -0
- package/client/dist/assets/bat-CcJ-xyqL.js +1 -0
- package/client/dist/assets/bicep-z1WDCKYz.js +2 -0
- package/client/dist/assets/browser-5ErDlJoR.js +1 -0
- package/client/dist/assets/browser-Bc-YdlVg.js +1 -0
- package/client/dist/assets/browser-BlYF4OOq.js +1 -0
- package/client/dist/assets/browser-CT-ReZGt.js +1 -0
- package/client/dist/assets/browser-DGITz3fC.js +1 -0
- package/client/dist/assets/browser-JsAIGCEW.js +1 -0
- package/client/dist/assets/browser-M5-rbPlw.js +1 -0
- package/client/dist/assets/browser-Qya9cARy.js +1 -0
- package/client/dist/assets/cameligo-BRewOpfa.js +1 -0
- package/client/dist/assets/chat-BEGuC03z.js +1 -0
- package/client/dist/assets/chat-BEW60P_u.js +1 -0
- package/client/dist/assets/chat-BQNMD0PL.js +1 -0
- package/client/dist/assets/chat-BsbNGPW9.js +1 -0
- package/client/dist/assets/chat-CboQguCi.js +1 -0
- package/client/dist/assets/chat-DRCa9pOt.js +1 -0
- package/client/dist/assets/chat-DwUm6W9z.js +1 -0
- package/client/dist/assets/chat-yoXwguQu.js +1 -0
- package/client/dist/assets/chunk-CilyBKbf.js +1 -0
- package/client/dist/assets/clojure-DBjRWN6g.js +1 -0
- package/client/dist/assets/clsx-DnqN-uhr.js +1 -0
- package/client/dist/assets/code-AL1rVIMb.js +1 -0
- package/client/dist/assets/code-C0BKpkht.js +1 -0
- package/client/dist/assets/code-C0FTS3ew.js +1 -0
- package/client/dist/assets/code-CPcHxzxw.js +1 -0
- package/client/dist/assets/code-D3ryDniw.js +1 -0
- package/client/dist/assets/code-D3zVVQTj.js +1 -0
- package/client/dist/assets/code-PCmfS3dn.js +1 -0
- package/client/dist/assets/code-exI0G5Wd.js +1 -0
- package/client/dist/assets/codicon-ngg6Pgfi.ttf +0 -0
- package/client/dist/assets/coffee-Cfk_XHGR.js +1 -0
- package/client/dist/assets/commands-B772IyDa.js +1 -0
- package/client/dist/assets/commands-BDDp6xFG.js +1 -0
- package/client/dist/assets/commands-CJxCry-o.js +1 -0
- package/client/dist/assets/commands-CfgY-_of.js +1 -0
- package/client/dist/assets/commands-DLrvnPNg.js +1 -0
- package/client/dist/assets/commands-IXMOKBYt.js +1 -0
- package/client/dist/assets/commands-UD1NzmwX.js +1 -0
- package/client/dist/assets/commands-sqrqsxyE.js +1 -0
- package/client/dist/assets/common-DCr6VzJ7.js +1 -0
- package/client/dist/assets/common-Dard9UNH.js +1 -0
- package/client/dist/assets/common-DeDELLZJ.js +1 -0
- package/client/dist/assets/common-DltqHaAe.js +1 -0
- package/client/dist/assets/common-Dmm1GhdD.js +1 -0
- package/client/dist/assets/common-DnjcgkPH.js +1 -0
- package/client/dist/assets/common-GbpxfPG8.js +1 -0
- package/client/dist/assets/common-wA36jmj1.js +1 -0
- package/client/dist/assets/cpp-BVob6BaP.js +1 -0
- package/client/dist/assets/csharp-C4fbRuOu.js +1 -0
- package/client/dist/assets/csp-DthFP_vT.js +1 -0
- package/client/dist/assets/css-CGMH0hcW.js +3 -0
- package/client/dist/assets/css.worker-Wv5dxAWO.js +89 -0
- package/client/dist/assets/cssMode-Cc6ozl-J.js +1 -0
- package/client/dist/assets/cypher-Pnf68BRV.js +1 -0
- package/client/dist/assets/dart-PMMOtxZX.js +1 -0
- package/client/dist/assets/dashboard-B4ixDVk8.js +1 -0
- package/client/dist/assets/dashboard-BZBADHSj.js +1 -0
- package/client/dist/assets/dashboard-C1MfeUHs.js +1 -0
- package/client/dist/assets/dashboard-C7SK6xu5.js +1 -0
- package/client/dist/assets/dashboard-CB6Le1yN.js +1 -0
- package/client/dist/assets/dashboard-CoTpMOBM.js +1 -0
- package/client/dist/assets/dashboard-Duo4DDCW.js +1 -0
- package/client/dist/assets/dashboard-I19DXBxw.js +1 -0
- package/client/dist/assets/dist-js-BY-Fv_fg.js +1 -0
- package/client/dist/assets/dist-js-Bakc4uxT.js +1 -0
- package/client/dist/assets/dockerfile-di1nsJCc.js +1 -0
- package/client/dist/assets/ecl-D_WVcB5M.js +1 -0
- package/client/dist/assets/editor-Br_kD0ds.css +1 -0
- package/client/dist/assets/editor.api2-XLGzZfbc.js +872 -0
- package/client/dist/assets/editor.main-CfXxHimg.js +6 -0
- package/client/dist/assets/editor.worker-Bd9IXS8d.js +26 -0
- package/client/dist/assets/elixir-OAdJEMOn.js +1 -0
- package/client/dist/assets/explore-4mFpnrKU.js +1 -0
- package/client/dist/assets/explore-A8Ltoblq.js +1 -0
- package/client/dist/assets/explore-B9A3iN2W.js +1 -0
- package/client/dist/assets/explore-BV5Xxlsn.js +1 -0
- package/client/dist/assets/explore-BrBJvfjP.js +1 -0
- package/client/dist/assets/explore-C3FSE42C.js +1 -0
- package/client/dist/assets/explore-D2EFgt8J.js +1 -0
- package/client/dist/assets/explore-hFc3HFcp.js +1 -0
- package/client/dist/assets/flow9-D3QEZjgn.js +1 -0
- package/client/dist/assets/format-command-CwGuwzGA.js +1 -0
- package/client/dist/assets/freemarker2-DP7J1gG3.js +3 -0
- package/client/dist/assets/fsharp-BF0k_8N8.js +1 -0
- package/client/dist/assets/go-BAQO5Jsz.js +1 -0
- package/client/dist/assets/graphql-hdFVFkiV.js +1 -0
- package/client/dist/assets/handlebars-BjRlucw6.js +1 -0
- package/client/dist/assets/hcl-DWnl1o-X.js +1 -0
- package/client/dist/assets/html-OumBQJ-U.js +1 -0
- package/client/dist/assets/html.worker-CQP8QQsS.js +502 -0
- package/client/dist/assets/htmlMode-CStc3zXM.js +1 -0
- package/client/dist/assets/index-CimDRRi7.css +2 -0
- package/client/dist/assets/index-XGZaKl_u.js +142 -0
- package/client/dist/assets/ini-CB-6OVu3.js +1 -0
- package/client/dist/assets/integrations-C3p12Ms6.js +1 -0
- package/client/dist/assets/integrations-Cr6hH7XR.js +1 -0
- package/client/dist/assets/integrations-Cublz3m6.js +1 -0
- package/client/dist/assets/integrations-D28q1kF6.js +1 -0
- package/client/dist/assets/integrations-DRdbki5W.js +1 -0
- package/client/dist/assets/integrations-DaC4SzzL.js +1 -0
- package/client/dist/assets/integrations-DmQYCUvN.js +1 -0
- package/client/dist/assets/integrations-HIlUxXVs.js +1 -0
- package/client/dist/assets/java-d1CmfiHX.js +1 -0
- package/client/dist/assets/javascript-CMk--e7g.js +1 -0
- package/client/dist/assets/jobs-BE1siB0M.js +1 -0
- package/client/dist/assets/jobs-BHcQ_Faf.js +1 -0
- package/client/dist/assets/jobs-CFfc2dNX.js +1 -0
- package/client/dist/assets/jobs-CSi5n8X_.js +1 -0
- package/client/dist/assets/jobs-Dc3X86PY.js +1 -0
- package/client/dist/assets/jobs-De5tASex.js +1 -0
- package/client/dist/assets/jobs-DsoXEdo7.js +1 -0
- package/client/dist/assets/jobs-Wl-ApPMb.js +1 -0
- package/client/dist/assets/json.worker-DzV-CpCQ.js +58 -0
- package/client/dist/assets/jsonMode-C2h3ZcjZ.js +7 -0
- package/client/dist/assets/julia-Bgv08lKa.js +1 -0
- package/client/dist/assets/kotlin-u98kaVTf.js +1 -0
- package/client/dist/assets/less-CjYwpgg5.js +2 -0
- package/client/dist/assets/lexon-YTjaAFBB.js +1 -0
- package/client/dist/assets/lib-CPxTMOAq.js +7 -0
- package/client/dist/assets/liquid-mI3KJrBE.js +1 -0
- package/client/dist/assets/lspLanguageFeatures-DU09ggWi.js +4 -0
- package/client/dist/assets/lua-BzmkWv27.js +1 -0
- package/client/dist/assets/m3-CFwk9fw0.js +1 -0
- package/client/dist/assets/markdown-CR5iMpSZ.js +1 -0
- package/client/dist/assets/mdx-C41VDTR_.js +1 -0
- package/client/dist/assets/mips-CcEalc17.js +1 -0
- package/client/dist/assets/monaco.contribution-CPObAXMC.js +2 -0
- package/client/dist/assets/msdax-BQbkawnr.js +1 -0
- package/client/dist/assets/mysql-GTlaaW_P.js +1 -0
- package/client/dist/assets/nav-0fwkrgHt.js +1 -0
- package/client/dist/assets/nav-BEL3MTwK.js +1 -0
- package/client/dist/assets/nav-B_G-TJDW.js +1 -0
- package/client/dist/assets/nav-C2YXcbZS.js +1 -0
- package/client/dist/assets/nav-ClzOE4mA.js +1 -0
- package/client/dist/assets/nav-CtYwmMgu.js +1 -0
- package/client/dist/assets/nav-D2bOGSEg.js +1 -0
- package/client/dist/assets/nav-iH1V5j6o.js +1 -0
- package/client/dist/assets/objective-c-Byu1T5if.js +1 -0
- package/client/dist/assets/pascal-BrfzBfRm.js +1 -0
- package/client/dist/assets/pascaligo-BXXKFUeo.js +1 -0
- package/client/dist/assets/perl-B3OikKq-.js +1 -0
- package/client/dist/assets/pgsql-CTsa0Acc.js +1 -0
- package/client/dist/assets/php-DiQh3FUW.js +1 -0
- package/client/dist/assets/pla-92uH8Fzm.js +1 -0
- package/client/dist/assets/postiats-BbeWkKUr.js +1 -0
- package/client/dist/assets/powerquery-DgDMzpsm.js +1 -0
- package/client/dist/assets/powershell-BfdUUzaG.js +1 -0
- package/client/dist/assets/preload-helper-DSXbuxSR.js +1 -0
- package/client/dist/assets/protobuf-BojW2ftW.js +2 -0
- package/client/dist/assets/pug-BxqTg3IU.js +1 -0
- package/client/dist/assets/python-Y27rKQtk.js +1 -0
- package/client/dist/assets/qsharp-BX_A-MW9.js +1 -0
- package/client/dist/assets/r-D9BMnxvJ.js +1 -0
- package/client/dist/assets/razor-Cd5-q9Bp.js +1 -0
- package/client/dist/assets/redis-5cJqEQJJ.js +1 -0
- package/client/dist/assets/redshift-d8BBqiwb.js +1 -0
- package/client/dist/assets/restructuredtext-C8a6yIcZ.js +1 -0
- package/client/dist/assets/ruby-egeh-6KX.js +1 -0
- package/client/dist/assets/rust-a3r9IInB.js +1 -0
- package/client/dist/assets/sb-y8iRIDei.js +1 -0
- package/client/dist/assets/scala-BPDK2AmK.js +1 -0
- package/client/dist/assets/scheme-BIWUEoOs.js +1 -0
- package/client/dist/assets/scss-CA-PSzwg.js +3 -0
- package/client/dist/assets/settings-55oDcbSh.js +1 -0
- package/client/dist/assets/settings-Bd4Tq1RB.js +1 -0
- package/client/dist/assets/settings-CCSM-Fhn.js +1 -0
- package/client/dist/assets/settings-D3e_bDoW.js +1 -0
- package/client/dist/assets/settings-DKbTkbn7.js +1 -0
- package/client/dist/assets/settings-Dxpo6_w7.js +1 -0
- package/client/dist/assets/settings-bt84e3Aa.js +1 -0
- package/client/dist/assets/settings-nu68QukM.js +1 -0
- package/client/dist/assets/setup-BMqwfbW9.js +1 -0
- package/client/dist/assets/setup-Bb5LcG28.js +1 -0
- package/client/dist/assets/setup-BeEx2_da.js +1 -0
- package/client/dist/assets/setup-CCCrB53Q.js +1 -0
- package/client/dist/assets/setup-CJA0ATmd.js +1 -0
- package/client/dist/assets/setup-CeiDbZcb.js +1 -0
- package/client/dist/assets/setup-Cus7TApA.js +1 -0
- package/client/dist/assets/setup-D9qOs2Xo.js +1 -0
- package/client/dist/assets/shell--LiT1Bja.js +1 -0
- package/client/dist/assets/solidity-DdqZccZg.js +1 -0
- package/client/dist/assets/sophia-S6-YxNG_.js +1 -0
- package/client/dist/assets/sparql-BSf5kMp2.js +1 -0
- package/client/dist/assets/specs-BFfu3u-a.js +1 -0
- package/client/dist/assets/specs-B__C8-8a.js +1 -0
- package/client/dist/assets/specs-CZ1PsXsC.js +1 -0
- package/client/dist/assets/specs-D2FzlLn9.js +1 -0
- package/client/dist/assets/specs-DaUTrNF9.js +1 -0
- package/client/dist/assets/specs-Dyc5hYeE.js +1 -0
- package/client/dist/assets/specs-cKEh2LXt.js +1 -0
- package/client/dist/assets/specs-k0PyLDVt.js +1 -0
- package/client/dist/assets/sql-D7KgjR8G.js +1 -0
- package/client/dist/assets/st-BnoDa-Ml.js +1 -0
- package/client/dist/assets/swift-DEUHTkUX.js +1 -0
- package/client/dist/assets/systemverilog-Tqb_KPnW.js +1 -0
- package/client/dist/assets/tcl-BmBFS2qq.js +1 -0
- package/client/dist/assets/terminal-80yDMgMF.js +1 -0
- package/client/dist/assets/terminal-Bje4ziIa.js +1 -0
- package/client/dist/assets/terminal-C2WYcFHF.js +1 -0
- package/client/dist/assets/terminal-CSONJOex.js +1 -0
- package/client/dist/assets/terminal-DEqzGtcr.js +1 -0
- package/client/dist/assets/terminal-DeWzh6ys.js +1 -0
- package/client/dist/assets/terminal-YOlsJCQj.js +1 -0
- package/client/dist/assets/terminal-lkZYR4wJ.js +1 -0
- package/client/dist/assets/tickets-CB7N30gm.js +1 -0
- package/client/dist/assets/tickets-CF2PYelu.js +1 -0
- package/client/dist/assets/tickets-DNOANUXr.js +1 -0
- package/client/dist/assets/tickets-DU1aqsbr.js +1 -0
- package/client/dist/assets/tickets-DYvafSaY.js +1 -0
- package/client/dist/assets/tickets-DlpC_iTg.js +1 -0
- package/client/dist/assets/tickets-DucYgtdl.js +1 -0
- package/client/dist/assets/tickets-clefmXLv.js +1 -0
- package/client/dist/assets/ts.worker-METxwbDZ.js +67719 -0
- package/client/dist/assets/tsMode-B0y_xEci.js +11 -0
- package/client/dist/assets/twig-BQV8igWC.js +1 -0
- package/client/dist/assets/typescript-BzK0OgwW.js +1 -0
- package/client/dist/assets/typespec-DlFroUGY.js +1 -0
- package/client/dist/assets/useProjectCache-DSaiGFjV.js +1 -0
- package/client/dist/assets/vb-BlrJpIMX.js +1 -0
- package/client/dist/assets/wgsl-BWgIc6FZ.js +298 -0
- package/client/dist/assets/workers-rt--R2Qy.js +1 -0
- package/client/dist/assets/xml-eX9QXAmI.js +1 -0
- package/client/dist/assets/yaml-fcsNkpOt.js +1 -0
- package/client/dist/index.html +246 -0
- package/docs/README.md +54 -0
- package/docs/cli.md +198 -0
- package/docs/codex.md +210 -0
- package/docs/creating-specs.md +197 -0
- package/docs/customizing.md +197 -0
- package/docs/getting-started.md +140 -0
- package/docs/internals/README.md +25 -0
- package/docs/internals/adding-a-provider.md +238 -0
- package/docs/internals/api-reference.md +634 -0
- package/docs/internals/architecture.md +332 -0
- package/docs/internals/configuration.md +172 -0
- package/docs/internals/openspec-workflow.md +282 -0
- package/docs/internals/operations-runbook.md +198 -0
- package/docs/internals/profiles.md +152 -0
- package/docs/platforms/macos.md +130 -0
- package/docs/platforms/windows.md +81 -0
- package/docs/running-pipelines.md +240 -0
- package/docs/terminal.md +138 -0
- package/docs/tracking-cost.md +155 -0
- package/package.json +82 -0
- package/server/dist/agent-generator.js +232 -0
- package/server/dist/agent-refine-db.js +124 -0
- package/server/dist/agent-refine-manager.js +526 -0
- package/server/dist/ai-invocations.js +111 -0
- package/server/dist/attachment-manager.js +299 -0
- package/server/dist/auth.js +207 -0
- package/server/dist/binary-probe.js +35 -0
- package/server/dist/browser-capture-manager.js +576 -0
- package/server/dist/browser-capture-types.js +28 -0
- package/server/dist/browser-network.js +149 -0
- package/server/dist/browser-playwright.js +888 -0
- package/server/dist/build-dirs.js +44 -0
- package/server/dist/changes-reader.js +120 -0
- package/server/dist/chat-manager.js +1060 -0
- package/server/dist/chromium-resolver.js +311 -0
- package/server/dist/code-explorer-router.js +788 -0
- package/server/dist/codex-otel-bridge.js +235 -0
- package/server/dist/command-resolver.js +102 -0
- package/server/dist/config.js +306 -0
- package/server/dist/context-budget.js +113 -0
- package/server/dist/context-scope.js +279 -0
- package/server/dist/contract-refine-runner.js +521 -0
- package/server/dist/core-compat.js +207 -0
- package/server/dist/core-package.js +14 -0
- package/server/dist/db.js +1034 -0
- package/server/dist/desktop-analytics.js +156 -0
- package/server/dist/desktop-db.js +456 -0
- package/server/dist/desktop-router.js +735 -0
- package/server/dist/docs-router.js +207 -0
- package/server/dist/explore-contract-refine.js +421 -0
- package/server/dist/explore-cwd-manager.js +242 -0
- package/server/dist/explore-draft-title.js +47 -0
- package/server/dist/explore-smash.js +450 -0
- package/server/dist/feature-flags.js +17 -0
- package/server/dist/file-provenance.js +382 -0
- package/server/dist/file-summary-generator.js +221 -0
- package/server/dist/file-summary-manager.js +689 -0
- package/server/dist/hooks.js +102 -0
- package/server/dist/ids.js +7 -0
- package/server/dist/index.js +586 -0
- package/server/dist/metrics.js +136 -0
- package/server/dist/mobile/index.js +16 -0
- package/server/dist/mobile/mobile-admin-router.js +84 -0
- package/server/dist/mobile/mobile-auth.js +67 -0
- package/server/dist/mobile/mobile-devices.js +80 -0
- package/server/dist/mobile/mobile-event-bus.js +39 -0
- package/server/dist/mobile/mobile-gateway.js +285 -0
- package/server/dist/mobile/mobile-mdns.js +81 -0
- package/server/dist/mobile/mobile-pairing.js +179 -0
- package/server/dist/mobile/mobile-redact.js +53 -0
- package/server/dist/mobile/mobile-router.js +411 -0
- package/server/dist/mobile/mobile-tls.js +86 -0
- package/server/dist/mobile/mobile-types.js +9 -0
- package/server/dist/mobile/mobile-ws.js +275 -0
- package/server/dist/path-resolver.js +298 -0
- package/server/dist/plugin-manager.js +617 -0
- package/server/dist/plugins/claude-approval.js +179 -0
- package/server/dist/plugins/claude-md-mutation.js +146 -0
- package/server/dist/plugins/codex-mcp.js +108 -0
- package/server/dist/plugins/contributors.js +72 -0
- package/server/dist/plugins/drift.js +58 -0
- package/server/dist/plugins/index.js +14 -0
- package/server/dist/plugins/json-mutation.js +120 -0
- package/server/dist/plugins/manager.js +32 -0
- package/server/dist/plugins/ownership.js +86 -0
- package/server/dist/plugins/paths.js +37 -0
- package/server/dist/plugins/prereq-installer.js +104 -0
- package/server/dist/plugins/rail-integration.js +79 -0
- package/server/dist/plugins/serena/index.js +13 -0
- package/server/dist/plugins/serena/install.js +91 -0
- package/server/dist/plugins/serena/instructions-content.js +21 -0
- package/server/dist/plugins/serena/manifest.js +111 -0
- package/server/dist/plugins/serena/verify.js +78 -0
- package/server/dist/plugins-router.js +215 -0
- package/server/dist/pricing.js +89 -0
- package/server/dist/profile-manager.js +310 -0
- package/server/dist/profiles-router.js +759 -0
- package/server/dist/project-registry.js +443 -0
- package/server/dist/project-router.js +4016 -0
- package/server/dist/proposal-manager.js +291 -0
- package/server/dist/provider-selection.js +69 -0
- package/server/dist/providers/claude-adapter.js +281 -0
- package/server/dist/providers/codex-adapter.js +264 -0
- package/server/dist/providers/index.js +23 -0
- package/server/dist/providers/registry.js +37 -0
- package/server/dist/providers/types.js +22 -0
- package/server/dist/queue-manager.js +1511 -0
- package/server/dist/rails-router.js +362 -0
- package/server/dist/rails-store.js +116 -0
- package/server/dist/result-event.js +106 -0
- package/server/dist/schemas/profile.v1.json +151 -0
- package/server/dist/setup-manager.js +1165 -0
- package/server/dist/setup-prerequisites.js +372 -0
- package/server/dist/smash-runner.js +663 -0
- package/server/dist/spec-draft-parser.js +133 -0
- package/server/dist/spec-launcher-manager.js +174 -0
- package/server/dist/spec-models.js +32 -0
- package/server/dist/specrails-tech-client.js +82 -0
- package/server/dist/spending.js +448 -0
- package/server/dist/telemetry-compactor.js +180 -0
- package/server/dist/telemetry-export.js +317 -0
- package/server/dist/telemetry-receiver.js +224 -0
- package/server/dist/terminal-manager.js +633 -0
- package/server/dist/terminal-marks-store.js +117 -0
- package/server/dist/terminal-osc-parser.js +159 -0
- package/server/dist/terminal-settings.js +282 -0
- package/server/dist/terminal-shell-integration.js +196 -0
- package/server/dist/ticket-broadcast.js +47 -0
- package/server/dist/ticket-store.js +397 -0
- package/server/dist/ticket-watcher.js +117 -0
- package/server/dist/types.js +10 -0
- package/server/dist/user-mcp-config.js +117 -0
- package/server/dist/util/cli-prompt.js +181 -0
- package/server/dist/util/secure-fs.js +50 -0
- package/server/dist/util/win-spawn.js +43 -0
- package/server/dist/webhook-manager.js +89 -0
- package/server/dist/ws-routing.js +47 -0
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createRailsRouter = createRailsRouter;
|
|
4
|
+
const express_1 = require("express");
|
|
5
|
+
const rails_store_1 = require("./rails-store");
|
|
6
|
+
const queue_manager_1 = require("./queue-manager");
|
|
7
|
+
const provider_selection_1 = require("./provider-selection");
|
|
8
|
+
const VALID_MODES = new Set(['implement', 'batch-implement', 'ultracode']);
|
|
9
|
+
// Models the ultracode picker exposes (Claude aliases). Mirrors the client
|
|
10
|
+
// RailModelSelector options and the project-router orchestrator-model allow-list.
|
|
11
|
+
const VALID_ULTRACODE_MODELS = new Set(['haiku', 'sonnet', 'opus']);
|
|
12
|
+
function createRailsRouter() {
|
|
13
|
+
const router = (0, express_1.Router)({ mergeParams: true });
|
|
14
|
+
function ctx(req) {
|
|
15
|
+
return req.projectCtx;
|
|
16
|
+
}
|
|
17
|
+
// Broadcast the full post-mutation rail snapshot so every connected client
|
|
18
|
+
// (desktop dashboard + mobile companion) reflects non-launch rail changes
|
|
19
|
+
// live — ticket reassignments, renames, mode/profile/engine edits. Re-reads
|
|
20
|
+
// the canonical rail (getRail) so the snapshot always carries the CURRENT
|
|
21
|
+
// name — a mutation return value (e.g. setRailTickets) omits it, which would
|
|
22
|
+
// otherwise broadcast name:null and clobber the rail's label on receivers.
|
|
23
|
+
function broadcastRailUpdated(c, railIndex, changed) {
|
|
24
|
+
const rail = (0, rails_store_1.getRail)(c.db, railIndex);
|
|
25
|
+
const msg = {
|
|
26
|
+
type: 'rail.updated',
|
|
27
|
+
projectId: c.project.id,
|
|
28
|
+
railIndex: rail.railIndex,
|
|
29
|
+
changed,
|
|
30
|
+
ticketIds: rail.ticketIds,
|
|
31
|
+
name: rail.name ?? null,
|
|
32
|
+
mode: rail.mode,
|
|
33
|
+
profileName: rail.profileName ?? null,
|
|
34
|
+
aiEngine: rail.aiEngine ?? null,
|
|
35
|
+
};
|
|
36
|
+
c.broadcast(msg);
|
|
37
|
+
}
|
|
38
|
+
// GET /rails — list all rail assignments + active job info
|
|
39
|
+
router.get('/', (_req, res) => {
|
|
40
|
+
const c = ctx(_req);
|
|
41
|
+
try {
|
|
42
|
+
const rails = (0, rails_store_1.getRails)(c.db);
|
|
43
|
+
// Include which rails have active jobs (so clients can reconcile stale 'running' state)
|
|
44
|
+
const activeJobs = {};
|
|
45
|
+
for (const [jobId, meta] of c.railJobs.entries()) {
|
|
46
|
+
activeJobs[meta.railIndex] = { jobId, mode: meta.mode };
|
|
47
|
+
}
|
|
48
|
+
res.json({ rails, activeJobs });
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
console.error('[rails-router] get rails error:', err);
|
|
52
|
+
res.status(500).json({ error: 'Failed to fetch rails' });
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
// PUT /rails/:railIndex/tickets — set ticket assignments for a rail
|
|
56
|
+
router.put('/:railIndex/tickets', (req, res) => {
|
|
57
|
+
const railIndex = parseInt(req.params.railIndex, 10);
|
|
58
|
+
if (isNaN(railIndex) || railIndex < 0) {
|
|
59
|
+
res.status(400).json({ error: 'Invalid rail index' });
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const { ticketIds } = req.body ?? {};
|
|
63
|
+
if (!Array.isArray(ticketIds) || ticketIds.some((id) => typeof id !== 'number')) {
|
|
64
|
+
res.status(400).json({ error: 'ticketIds must be an array of numbers' });
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const c = ctx(req);
|
|
68
|
+
try {
|
|
69
|
+
// setRailTickets does delete-then-reinsert; without forwarding the rail's
|
|
70
|
+
// current mode/profileName they would reset to defaults ('implement' /
|
|
71
|
+
// null) on every ticket reassignment, silently wiping a configured
|
|
72
|
+
// per-rail profile. Preserve them (an explicit body value still wins).
|
|
73
|
+
const current = (0, rails_store_1.getRail)(c.db, railIndex);
|
|
74
|
+
const body = req.body ?? {};
|
|
75
|
+
const mode = typeof body.mode === 'string' ? body.mode : current.mode;
|
|
76
|
+
const profileName = 'profileName' in body ? body.profileName : current.profileName;
|
|
77
|
+
// Preserve the rail's AI engine across ticket reassignment (undefined →
|
|
78
|
+
// setRailTickets re-reads the current value), so it isn't silently wiped.
|
|
79
|
+
const aiEngine = 'aiEngine' in body ? body.aiEngine : undefined;
|
|
80
|
+
const rail = (0, rails_store_1.setRailTickets)(c.db, railIndex, ticketIds, mode, profileName, aiEngine);
|
|
81
|
+
broadcastRailUpdated(c, railIndex, 'tickets');
|
|
82
|
+
res.json({ rail });
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
console.error('[rails-router] set rail tickets error:', err);
|
|
86
|
+
res.status(500).json({ error: 'Failed to update rail tickets' });
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
// PUT /rails/:railIndex/profile — set the default agent profile for a rail
|
|
90
|
+
// Body: { profileName: string | null } (null = force legacy mode for this rail)
|
|
91
|
+
router.put('/:railIndex/profile', (req, res) => {
|
|
92
|
+
const railIndex = parseInt(req.params.railIndex, 10);
|
|
93
|
+
if (isNaN(railIndex) || railIndex < 0) {
|
|
94
|
+
res.status(400).json({ error: 'Invalid rail index' });
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const body = req.body ?? {};
|
|
98
|
+
if (!('profileName' in body)) {
|
|
99
|
+
res.status(400).json({ error: "body must include 'profileName' (string or null)" });
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const value = body.profileName;
|
|
103
|
+
if (value !== null && typeof value !== 'string') {
|
|
104
|
+
res.status(400).json({ error: 'profileName must be a string or null' });
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const c = ctx(req);
|
|
108
|
+
try {
|
|
109
|
+
const rail = (0, rails_store_1.setRailProfile)(c.db, railIndex, value);
|
|
110
|
+
broadcastRailUpdated(c, railIndex, 'profile');
|
|
111
|
+
res.json({ rail });
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
console.error('[rails-router] set rail profile error:', err);
|
|
115
|
+
res.status(500).json({ error: 'Failed to update rail profile' });
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
// PUT /rails/:railIndex/engine — set the AI engine override for a rail
|
|
119
|
+
// Body: { aiEngine: string | null } (null = use the project's primary provider)
|
|
120
|
+
router.put('/:railIndex/engine', (req, res) => {
|
|
121
|
+
const railIndex = parseInt(req.params.railIndex, 10);
|
|
122
|
+
if (isNaN(railIndex) || railIndex < 0) {
|
|
123
|
+
res.status(400).json({ error: 'Invalid rail index' });
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const body = req.body ?? {};
|
|
127
|
+
if (!('aiEngine' in body)) {
|
|
128
|
+
res.status(400).json({ error: "body must include 'aiEngine' (string or null)" });
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const value = body.aiEngine;
|
|
132
|
+
const c = ctx(req);
|
|
133
|
+
// null clears the override; a string must be one of the project's providers.
|
|
134
|
+
if (value !== null) {
|
|
135
|
+
const check = (0, provider_selection_1.validateRequestedProvider)(c.project, value);
|
|
136
|
+
if (!check.ok) {
|
|
137
|
+
res.status(400).json({ error: check.error });
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
try {
|
|
142
|
+
const rail = (0, rails_store_1.setRailEngine)(c.db, railIndex, value);
|
|
143
|
+
broadcastRailUpdated(c, railIndex, 'engine');
|
|
144
|
+
res.json({ rail });
|
|
145
|
+
}
|
|
146
|
+
catch (err) {
|
|
147
|
+
console.error('[rails-router] set rail engine error:', err);
|
|
148
|
+
res.status(500).json({ error: 'Failed to update rail engine' });
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
// PUT /rails/:railIndex/name — set the rail's display name (the "Rail "-suffix)
|
|
152
|
+
// Body: { name: string | null } (empty/null clears it back to the default label)
|
|
153
|
+
router.put('/:railIndex/name', (req, res) => {
|
|
154
|
+
const railIndex = parseInt(req.params.railIndex, 10);
|
|
155
|
+
if (isNaN(railIndex) || railIndex < 0) {
|
|
156
|
+
res.status(400).json({ error: 'Invalid rail index' });
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
const body = req.body ?? {};
|
|
160
|
+
if (!('name' in body)) {
|
|
161
|
+
res.status(400).json({ error: "body must include 'name' (string or null)" });
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
const value = body.name;
|
|
165
|
+
if (value !== null && typeof value !== 'string') {
|
|
166
|
+
res.status(400).json({ error: 'name must be a string or null' });
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
// Guard against unbounded labels (UI shows a short chip).
|
|
170
|
+
if (typeof value === 'string' && value.length > 60) {
|
|
171
|
+
res.status(400).json({ error: 'name must be 60 characters or fewer' });
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
const c = ctx(req);
|
|
175
|
+
try {
|
|
176
|
+
const rail = (0, rails_store_1.setRailName)(c.db, railIndex, value);
|
|
177
|
+
broadcastRailUpdated(c, railIndex, 'name');
|
|
178
|
+
res.json({ rail });
|
|
179
|
+
}
|
|
180
|
+
catch (err) {
|
|
181
|
+
console.error('[rails-router] set rail name error:', err);
|
|
182
|
+
res.status(500).json({ error: 'Failed to update rail name' });
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
// POST /rails/:railIndex/launch — launch job(s) for a rail
|
|
186
|
+
router.post('/:railIndex/launch', (req, res) => {
|
|
187
|
+
const railIndex = parseInt(req.params.railIndex, 10);
|
|
188
|
+
if (isNaN(railIndex) || railIndex < 0) {
|
|
189
|
+
res.status(400).json({ error: 'Invalid rail index' });
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
const { mode = 'implement', profileName, aiEngine, model } = req.body ?? {};
|
|
193
|
+
if (!VALID_MODES.has(mode)) {
|
|
194
|
+
res.status(400).json({ error: 'mode must be "implement", "batch-implement" or "ultracode"' });
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
// Ultracode model picker: optional, validated against the allow-list.
|
|
198
|
+
// Ignored for non-ultracode modes (they use the orchestrator model).
|
|
199
|
+
if (mode === 'ultracode' && model !== undefined && model !== null) {
|
|
200
|
+
if (typeof model !== 'string' || !VALID_ULTRACODE_MODELS.has(model)) {
|
|
201
|
+
res.status(400).json({ error: 'model must be one of: haiku, sonnet, opus' });
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
const c = ctx(req);
|
|
206
|
+
const rail = (0, rails_store_1.getRail)(c.db, railIndex);
|
|
207
|
+
if (rail.ticketIds.length === 0) {
|
|
208
|
+
res.status(400).json({ error: 'Rail has no tickets assigned' });
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
// AI engine precedence: explicit body param > stored rail engine > primary.
|
|
212
|
+
// `undefined`/empty in both means "run on the project's primary provider".
|
|
213
|
+
const requestedEngine = aiEngine === undefined ? (rail.aiEngine ?? undefined) : aiEngine;
|
|
214
|
+
const engineCheck = (0, provider_selection_1.validateRequestedProvider)(c.project, requestedEngine);
|
|
215
|
+
if (!engineCheck.ok) {
|
|
216
|
+
res.status(400).json({ error: engineCheck.error });
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
// Only pass a provider override when one was actually requested (keeps
|
|
220
|
+
// single-provider rails on the legacy code path).
|
|
221
|
+
const railProvider = requestedEngine ? engineCheck.provider : undefined;
|
|
222
|
+
// Ultracode bypasses the OpenSpec pipeline and hands the raw spec to
|
|
223
|
+
// Claude. It is Claude-only — reject when the effective engine is not claude.
|
|
224
|
+
if (mode === 'ultracode' && engineCheck.provider !== 'claude') {
|
|
225
|
+
res.status(400).json({ error: 'Ultracode requires the Claude provider' });
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
// Profile selection precedence: explicit body param > stored rail profile > default resolution.
|
|
229
|
+
// `null` in the body explicitly forces legacy mode. Codex has no agent
|
|
230
|
+
// profiles, so force legacy mode whenever the chosen engine is not claude.
|
|
231
|
+
let resolvedProfile;
|
|
232
|
+
if (mode === 'ultracode') {
|
|
233
|
+
// Ultracode runs no agent pipeline, so profiles do not apply.
|
|
234
|
+
resolvedProfile = null;
|
|
235
|
+
}
|
|
236
|
+
else if (railProvider && railProvider !== 'claude') {
|
|
237
|
+
resolvedProfile = null;
|
|
238
|
+
}
|
|
239
|
+
else if (profileName === null) {
|
|
240
|
+
resolvedProfile = null;
|
|
241
|
+
}
|
|
242
|
+
else if (typeof profileName === 'string' && profileName.trim()) {
|
|
243
|
+
resolvedProfile = profileName.trim();
|
|
244
|
+
}
|
|
245
|
+
else if (rail.profileName) {
|
|
246
|
+
resolvedProfile = rail.profileName;
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
resolvedProfile = undefined; // fall through to QueueManager default resolution
|
|
250
|
+
}
|
|
251
|
+
try {
|
|
252
|
+
let jobId;
|
|
253
|
+
if (mode === 'ultracode') {
|
|
254
|
+
// Ultracode launches ONE independent Claude job per ticket — each gets
|
|
255
|
+
// its own log and runs the spec autonomously (no pipeline). The rail UI
|
|
256
|
+
// tracks the first job as its representative active job; every job is
|
|
257
|
+
// registered so its ticket is marked done on completion.
|
|
258
|
+
// `provider: 'claude'` is explicit so the spawn resolves the claude
|
|
259
|
+
// adapter regardless of the project's primary.
|
|
260
|
+
const ultracodeModel = mode === 'ultracode' && typeof model === 'string' && VALID_ULTRACODE_MODELS.has(model)
|
|
261
|
+
? model
|
|
262
|
+
: undefined;
|
|
263
|
+
const jobIds = [];
|
|
264
|
+
for (const ticketId of rail.ticketIds) {
|
|
265
|
+
const command = `/specrails:ultracode #${ticketId} --yes`;
|
|
266
|
+
const job = c.queueManager.enqueue(command, 'normal', {
|
|
267
|
+
profileName: null,
|
|
268
|
+
provider: 'claude',
|
|
269
|
+
...(ultracodeModel ? { model: ultracodeModel } : {}),
|
|
270
|
+
});
|
|
271
|
+
jobIds.push(job.id);
|
|
272
|
+
c.railJobs.set(job.id, { railIndex, mode, ticketIds: [ticketId] });
|
|
273
|
+
}
|
|
274
|
+
jobId = jobIds[0];
|
|
275
|
+
const startMsg = {
|
|
276
|
+
type: 'rail.job_started',
|
|
277
|
+
projectId: c.project.id,
|
|
278
|
+
railIndex,
|
|
279
|
+
jobId,
|
|
280
|
+
mode,
|
|
281
|
+
};
|
|
282
|
+
c.broadcast(startMsg);
|
|
283
|
+
res.status(202).json({ jobId, jobIds, railIndex, mode });
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
// Implement / batch-implement create a single job with all ticket IDs.
|
|
287
|
+
// /specrails:implement handles multiple specs in parallel internally.
|
|
288
|
+
const issueArgs = rail.ticketIds.map((id) => `#${id}`).join(' ');
|
|
289
|
+
const commandName = mode === 'batch-implement' ? 'batch-implement' : 'implement';
|
|
290
|
+
const command = `/specrails:${commandName} ${issueArgs} --yes`;
|
|
291
|
+
const job = c.queueManager.enqueue(command, 'normal', { profileName: resolvedProfile, provider: railProvider });
|
|
292
|
+
jobId = job.id;
|
|
293
|
+
c.railJobs.set(jobId, { railIndex, mode, ticketIds: [...rail.ticketIds] });
|
|
294
|
+
const startMsg = {
|
|
295
|
+
type: 'rail.job_started',
|
|
296
|
+
projectId: c.project.id,
|
|
297
|
+
railIndex,
|
|
298
|
+
jobId,
|
|
299
|
+
mode,
|
|
300
|
+
};
|
|
301
|
+
c.broadcast(startMsg);
|
|
302
|
+
res.status(202).json({ jobId, railIndex, mode });
|
|
303
|
+
}
|
|
304
|
+
catch (err) {
|
|
305
|
+
if (err instanceof queue_manager_1.ClaudeNotFoundError) {
|
|
306
|
+
res.status(503).json({ error: 'Claude CLI not found' });
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
if (err instanceof queue_manager_1.CodexNotFoundError) {
|
|
310
|
+
res.status(503).json({ error: 'Codex CLI not found' });
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
console.error('[rails-router] launch error:', err);
|
|
314
|
+
res.status(500).json({ error: 'Failed to launch rail job' });
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
// POST /rails/:railIndex/stop — kill the running job for a rail
|
|
318
|
+
router.post('/:railIndex/stop', (req, res) => {
|
|
319
|
+
const railIndex = parseInt(req.params.railIndex, 10);
|
|
320
|
+
if (isNaN(railIndex) || railIndex < 0) {
|
|
321
|
+
res.status(400).json({ error: 'Invalid rail index' });
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
const c = ctx(req);
|
|
325
|
+
// M19: an Ultracode rail registers ONE queue job per ticket. The old code
|
|
326
|
+
// stopped only the FIRST matching job, so the remaining N-1 jobs kept running
|
|
327
|
+
// and billing while the UI showed the rail stopped. Collect ALL jobs for this
|
|
328
|
+
// rail index and cancel each (running → kill, queued → cancel).
|
|
329
|
+
const targetJobIds = Array.from(c.railJobs.entries())
|
|
330
|
+
.filter(([, meta]) => meta.railIndex === railIndex)
|
|
331
|
+
.map(([jobId]) => jobId);
|
|
332
|
+
if (targetJobIds.length === 0) {
|
|
333
|
+
res.status(404).json({ error: 'No active rail job found for this rail' });
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
let canceledCount = 0;
|
|
337
|
+
for (const jobId of targetJobIds) {
|
|
338
|
+
try {
|
|
339
|
+
c.queueManager.cancel(jobId);
|
|
340
|
+
canceledCount++;
|
|
341
|
+
}
|
|
342
|
+
catch (err) {
|
|
343
|
+
// Already terminal / unknown — clean up the stale entry regardless so the
|
|
344
|
+
// rail card can't get wedged 'running' (this was the unrecoverable case).
|
|
345
|
+
console.warn(`[rails-router] stop: cancel(${jobId}) failed: ${err.message}`);
|
|
346
|
+
}
|
|
347
|
+
c.railJobs.delete(jobId);
|
|
348
|
+
}
|
|
349
|
+
// Broadcast one stop per job so every rail card reconciles.
|
|
350
|
+
for (const jobId of targetJobIds) {
|
|
351
|
+
const stopMsg = {
|
|
352
|
+
type: 'rail.job_stopped',
|
|
353
|
+
projectId: c.project.id,
|
|
354
|
+
railIndex,
|
|
355
|
+
jobId,
|
|
356
|
+
};
|
|
357
|
+
c.broadcast(stopMsg);
|
|
358
|
+
}
|
|
359
|
+
res.json({ ok: true, jobIds: targetJobIds, canceled: canceledCount });
|
|
360
|
+
});
|
|
361
|
+
return router;
|
|
362
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getRails = getRails;
|
|
4
|
+
exports.getRail = getRail;
|
|
5
|
+
exports.setRailTickets = setRailTickets;
|
|
6
|
+
exports.setRailProfile = setRailProfile;
|
|
7
|
+
exports.setRailEngine = setRailEngine;
|
|
8
|
+
exports.setRailName = setRailName;
|
|
9
|
+
/** Read the per-rail display names from rail_meta into a lookup map. */
|
|
10
|
+
function railNames(db) {
|
|
11
|
+
const rows = db.prepare('SELECT rail_index, name FROM rail_meta').all();
|
|
12
|
+
const map = new Map();
|
|
13
|
+
for (const r of rows)
|
|
14
|
+
map.set(r.rail_index, r.name);
|
|
15
|
+
return map;
|
|
16
|
+
}
|
|
17
|
+
// ─── Queries ─────────────────────────────────────────────────────────────────
|
|
18
|
+
function getRails(db) {
|
|
19
|
+
const rows = db
|
|
20
|
+
.prepare('SELECT rail_index, ticket_id, position, mode, profile_name, ai_engine FROM rails ORDER BY rail_index, position')
|
|
21
|
+
.all();
|
|
22
|
+
const map = new Map();
|
|
23
|
+
for (const row of rows) {
|
|
24
|
+
if (!map.has(row.rail_index)) {
|
|
25
|
+
map.set(row.rail_index, {
|
|
26
|
+
ticketIds: [],
|
|
27
|
+
mode: row.mode,
|
|
28
|
+
profileName: row.profile_name,
|
|
29
|
+
aiEngine: row.ai_engine,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
map.get(row.rail_index).ticketIds.push(row.ticket_id);
|
|
33
|
+
}
|
|
34
|
+
const names = railNames(db);
|
|
35
|
+
return [0, 1, 2].map((railIndex) => {
|
|
36
|
+
const rail = map.get(railIndex);
|
|
37
|
+
return {
|
|
38
|
+
railIndex,
|
|
39
|
+
ticketIds: rail?.ticketIds ?? [],
|
|
40
|
+
mode: rail?.mode ?? 'implement',
|
|
41
|
+
profileName: rail?.profileName ?? null,
|
|
42
|
+
aiEngine: rail?.aiEngine ?? null,
|
|
43
|
+
name: names.get(railIndex) ?? null,
|
|
44
|
+
};
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
function getRail(db, railIndex) {
|
|
48
|
+
const rows = db
|
|
49
|
+
.prepare('SELECT ticket_id, position, mode, profile_name, ai_engine FROM rails WHERE rail_index = ? ORDER BY position')
|
|
50
|
+
.all(railIndex);
|
|
51
|
+
const meta = db.prepare('SELECT name FROM rail_meta WHERE rail_index = ?').get(railIndex);
|
|
52
|
+
return {
|
|
53
|
+
railIndex,
|
|
54
|
+
ticketIds: rows.map((r) => r.ticket_id),
|
|
55
|
+
mode: rows[0]?.mode ?? 'implement',
|
|
56
|
+
profileName: rows[0]?.profile_name ?? null,
|
|
57
|
+
aiEngine: rows[0]?.ai_engine ?? null,
|
|
58
|
+
name: meta?.name ?? null,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
// ─── Mutations ────────────────────────────────────────────────────────────────
|
|
62
|
+
function setRailTickets(db, railIndex, ticketIds, mode, profileName, aiEngine) {
|
|
63
|
+
const deleteStmt = db.prepare('DELETE FROM rails WHERE rail_index = ?');
|
|
64
|
+
const insertStmt = db.prepare('INSERT INTO rails (rail_index, ticket_id, position, mode, profile_name, ai_engine) VALUES (?, ?, ?, ?, ?, ?)');
|
|
65
|
+
const resolvedMode = mode ?? 'implement';
|
|
66
|
+
const resolvedProfile = profileName === undefined ? null : profileName;
|
|
67
|
+
// undefined → preserve existing engine across re-orders; null → explicit clear.
|
|
68
|
+
const resolvedEngine = aiEngine === undefined ? (getRail(db, railIndex).aiEngine ?? null) : aiEngine;
|
|
69
|
+
db.transaction(() => {
|
|
70
|
+
deleteStmt.run(railIndex);
|
|
71
|
+
for (let i = 0; i < ticketIds.length; i++) {
|
|
72
|
+
insertStmt.run(railIndex, ticketIds[i], i, resolvedMode, resolvedProfile, resolvedEngine);
|
|
73
|
+
}
|
|
74
|
+
})();
|
|
75
|
+
return { railIndex, ticketIds, mode: resolvedMode, profileName: resolvedProfile, aiEngine: resolvedEngine };
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Update only the profile for a rail, preserving tickets and mode.
|
|
79
|
+
* No-op (returns current state) when the rail has no tickets yet — the
|
|
80
|
+
* profile is stored as a column on each rail row.
|
|
81
|
+
*/
|
|
82
|
+
function setRailProfile(db, railIndex, profileName) {
|
|
83
|
+
const current = getRail(db, railIndex);
|
|
84
|
+
if (current.ticketIds.length === 0) {
|
|
85
|
+
// No rows to update; insert a placeholder row? No — we store profile on
|
|
86
|
+
// each ticket row. Users must assign tickets first. Caller checks.
|
|
87
|
+
return { ...current, profileName };
|
|
88
|
+
}
|
|
89
|
+
db.prepare('UPDATE rails SET profile_name = ? WHERE rail_index = ?').run(profileName, railIndex);
|
|
90
|
+
return { ...current, profileName };
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Update only the AI engine for a rail, preserving tickets, mode and profile.
|
|
94
|
+
* Like setRailProfile, no-op (returns current state with the new engine) when
|
|
95
|
+
* the rail has no tickets yet — the engine is stored on each rail row.
|
|
96
|
+
*/
|
|
97
|
+
function setRailEngine(db, railIndex, aiEngine) {
|
|
98
|
+
const current = getRail(db, railIndex);
|
|
99
|
+
if (current.ticketIds.length === 0) {
|
|
100
|
+
return { ...current, aiEngine };
|
|
101
|
+
}
|
|
102
|
+
db.prepare('UPDATE rails SET ai_engine = ? WHERE rail_index = ?').run(aiEngine, railIndex);
|
|
103
|
+
return { ...current, aiEngine };
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Set a rail's display name (the "Rail "-suffix the user types). Stored in
|
|
107
|
+
* rail_meta (ticket-independent) so even an empty rail keeps its name. Pass
|
|
108
|
+
* an empty/whitespace name to clear it back to the default "Rail N" label.
|
|
109
|
+
*/
|
|
110
|
+
function setRailName(db, railIndex, name) {
|
|
111
|
+
const trimmed = typeof name === 'string' ? name.trim() : null;
|
|
112
|
+
const value = trimmed && trimmed.length > 0 ? trimmed : null;
|
|
113
|
+
db.prepare(`INSERT INTO rail_meta (rail_index, name) VALUES (?, ?)
|
|
114
|
+
ON CONFLICT(rail_index) DO UPDATE SET name = excluded.name`).run(railIndex, value);
|
|
115
|
+
return { ...getRail(db, railIndex), name: value };
|
|
116
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Result-event normalisation and finalisation for AI CLI invocations.
|
|
3
|
+
//
|
|
4
|
+
// Two coexisting APIs during the multi-provider migration window:
|
|
5
|
+
//
|
|
6
|
+
// - finaliseInvocationResult(adapter, events, opts) — new contract used by
|
|
7
|
+
// post-adapter managers. Walks an AdapterEvent stream, calls
|
|
8
|
+
// `adapter.extractResult`, and falls back to the local pricing table when
|
|
9
|
+
// the adapter declares `capabilities.nativeCostUsd === false`. Returns
|
|
10
|
+
// both the NormalisedResult and an `estimated` flag for the
|
|
11
|
+
// `total_cost_usd_estimated` DB column.
|
|
12
|
+
//
|
|
13
|
+
// - normaliseResultEvent(event, provider) — legacy single-event shape kept
|
|
14
|
+
// so non-migrated managers (queue-manager, chat-manager,
|
|
15
|
+
// agent-refine-manager pre-refactor) keep working. Will be removed after
|
|
16
|
+
// all callsites migrate (tasks §7–10).
|
|
17
|
+
//
|
|
18
|
+
// Spec: openspec/specs/multi-provider-architecture/spec.md
|
|
19
|
+
// Spec: openspec/changes/add-multi-provider-support/specs/project-spending/spec.md
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.finaliseInvocationResult = finaliseInvocationResult;
|
|
22
|
+
exports.normaliseResultEvent = normaliseResultEvent;
|
|
23
|
+
const pricing_1 = require("./pricing");
|
|
24
|
+
/**
|
|
25
|
+
* Finalise an AI invocation by walking the adapter's parsed events, stamping
|
|
26
|
+
* the model when the adapter does not report one, and applying the local
|
|
27
|
+
* pricing-table fallback when the adapter does not report `total_cost_usd`.
|
|
28
|
+
*/
|
|
29
|
+
function finaliseInvocationResult(adapter, events, opts = {}) {
|
|
30
|
+
const result = adapter.extractResult(events);
|
|
31
|
+
const cloned = { ...result };
|
|
32
|
+
// Stamp the model from the caller when the adapter did not derive one. The
|
|
33
|
+
// adapter contract permits leaving `model` undefined; the manager knows the
|
|
34
|
+
// model it spawned with.
|
|
35
|
+
if (!cloned.model && opts.fallbackModel) {
|
|
36
|
+
cloned.model = opts.fallbackModel;
|
|
37
|
+
}
|
|
38
|
+
let estimated = false;
|
|
39
|
+
if (!adapter.capabilities.nativeCostUsd) {
|
|
40
|
+
const estimator = opts.estimator ?? pricing_1.estimateCostUsd;
|
|
41
|
+
const computed = estimator(adapter.id, cloned.model, {
|
|
42
|
+
tokens_in: cloned.tokens_in,
|
|
43
|
+
tokens_out: cloned.tokens_out,
|
|
44
|
+
tokens_cache_read: cloned.tokens_cache_read,
|
|
45
|
+
tokens_cache_create: cloned.tokens_cache_create,
|
|
46
|
+
});
|
|
47
|
+
if (computed !== null) {
|
|
48
|
+
cloned.total_cost_usd = computed;
|
|
49
|
+
estimated = true;
|
|
50
|
+
}
|
|
51
|
+
else if (cloned.model) {
|
|
52
|
+
// Non-native-cost provider with a known model id but no pricing-table
|
|
53
|
+
// entry → cost is persisted NULL and silently vanishes from totals.
|
|
54
|
+
// Surface it so a new/renamed/drifted model id is caught fast (the fix
|
|
55
|
+
// is to add the rate to server/pricing.ts, per its quarterly-review
|
|
56
|
+
// contract). We deliberately do NOT fabricate a fallback rate.
|
|
57
|
+
console.warn(`[pricing] no rate-card entry for "${adapter.id}:${cloned.model}" — ` +
|
|
58
|
+
`cost will be persisted NULL. Add it to server/pricing.ts PRICING.`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return { result: cloned, estimated };
|
|
62
|
+
}
|
|
63
|
+
// ─── Legacy single-event API ─────────────────────────────────────────────────
|
|
64
|
+
//
|
|
65
|
+
// Pre-adapter managers still call this with the single terminal event payload
|
|
66
|
+
// they captured. Behaviour identical to the implementation that lived here
|
|
67
|
+
// before; provider-specific branches preserved. Once all callsites migrate to
|
|
68
|
+
// `finaliseInvocationResult`, delete this and remove the import.
|
|
69
|
+
function normaliseResultEvent(event, provider = 'claude') {
|
|
70
|
+
if (!event)
|
|
71
|
+
return {};
|
|
72
|
+
if (provider === 'claude') {
|
|
73
|
+
const usage = event.usage;
|
|
74
|
+
return {
|
|
75
|
+
tokens_in: usage?.input_tokens,
|
|
76
|
+
tokens_out: usage?.output_tokens,
|
|
77
|
+
tokens_cache_read: usage?.cache_read_input_tokens,
|
|
78
|
+
tokens_cache_create: usage?.cache_creation_input_tokens,
|
|
79
|
+
total_cost_usd: event.total_cost_usd,
|
|
80
|
+
num_turns: event.num_turns,
|
|
81
|
+
model: event.model,
|
|
82
|
+
duration_ms: event.duration_ms,
|
|
83
|
+
duration_api_ms: event.api_duration_ms,
|
|
84
|
+
session_id: event.session_id,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
// Codex path: callers may pass either a `turn.completed` event (with a
|
|
88
|
+
// nested `usage` block) or a synthesised result-like object with
|
|
89
|
+
// pre-flattened fields. Read whichever shape is present.
|
|
90
|
+
const usage = event.usage;
|
|
91
|
+
// Codex folds reasoning_output_tokens into the billed output token count
|
|
92
|
+
// (OpenAI bills reasoning tokens at output-token rates), so we collapse
|
|
93
|
+
// them here to keep cost estimation consistent with the adapter.
|
|
94
|
+
const outputTokens = usage
|
|
95
|
+
? (usage.output_tokens ?? 0) + (usage.reasoning_output_tokens ?? 0)
|
|
96
|
+
: undefined;
|
|
97
|
+
return {
|
|
98
|
+
tokens_in: usage?.input_tokens,
|
|
99
|
+
tokens_out: outputTokens,
|
|
100
|
+
tokens_cache_read: usage?.cached_input_tokens,
|
|
101
|
+
total_cost_usd: typeof event.total_cost_usd === 'number' ? event.total_cost_usd : undefined,
|
|
102
|
+
num_turns: typeof event.num_turns === 'number' ? event.num_turns : undefined,
|
|
103
|
+
model: typeof event.model === 'string' ? event.model : undefined,
|
|
104
|
+
duration_ms: typeof event.duration_ms === 'number' ? event.duration_ms : undefined,
|
|
105
|
+
};
|
|
106
|
+
}
|