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,1034 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.DEFAULT_ULTRACODE_PRE_PROMPT = void 0;
|
|
7
|
+
exports.initDb = initDb;
|
|
8
|
+
exports.createJob = createJob;
|
|
9
|
+
exports.finishJob = finishJob;
|
|
10
|
+
exports.appendEvent = appendEvent;
|
|
11
|
+
exports.upsertPhase = upsertPhase;
|
|
12
|
+
exports.listJobs = listJobs;
|
|
13
|
+
exports.getJob = getJob;
|
|
14
|
+
exports.getJobEvents = getJobEvents;
|
|
15
|
+
exports.deleteJob = deleteJob;
|
|
16
|
+
exports.purgeJobs = purgeJobs;
|
|
17
|
+
exports.getProjectActivity = getProjectActivity;
|
|
18
|
+
exports.createConversation = createConversation;
|
|
19
|
+
exports.listConversations = listConversations;
|
|
20
|
+
exports.getConversation = getConversation;
|
|
21
|
+
exports.deleteConversation = deleteConversation;
|
|
22
|
+
exports.updateConversation = updateConversation;
|
|
23
|
+
exports.addMessage = addMessage;
|
|
24
|
+
exports.getMessages = getMessages;
|
|
25
|
+
exports.createProposal = createProposal;
|
|
26
|
+
exports.getProposal = getProposal;
|
|
27
|
+
exports.listProposals = listProposals;
|
|
28
|
+
exports.updateProposal = updateProposal;
|
|
29
|
+
exports.deleteProposal = deleteProposal;
|
|
30
|
+
exports.createTemplate = createTemplate;
|
|
31
|
+
exports.listTemplates = listTemplates;
|
|
32
|
+
exports.getTemplate = getTemplate;
|
|
33
|
+
exports.updateTemplate = updateTemplate;
|
|
34
|
+
exports.deleteTemplate = deleteTemplate;
|
|
35
|
+
exports.skipJob = skipJob;
|
|
36
|
+
exports.getPipelineJobs = getPipelineJobs;
|
|
37
|
+
exports.getStats = getStats;
|
|
38
|
+
exports.getProjectSettings = getProjectSettings;
|
|
39
|
+
exports.getUltracodePrePrompt = getUltracodePrePrompt;
|
|
40
|
+
exports.updateProjectSettings = updateProjectSettings;
|
|
41
|
+
exports.getQuickContractRefineLast = getQuickContractRefineLast;
|
|
42
|
+
exports.hasQuickContractRefineLast = hasQuickContractRefineLast;
|
|
43
|
+
exports.setQuickContractRefineLast = setQuickContractRefineLast;
|
|
44
|
+
exports.getTelemetryBlob = getTelemetryBlob;
|
|
45
|
+
exports.upsertTelemetryBlob = upsertTelemetryBlob;
|
|
46
|
+
exports.listActiveTelemetryBlobs = listActiveTelemetryBlobs;
|
|
47
|
+
exports.setTelemetryBlobCompacted = setTelemetryBlobCompacted;
|
|
48
|
+
exports.insertTelemetrySummary = insertTelemetrySummary;
|
|
49
|
+
exports.getTelemetrySummaries = getTelemetrySummaries;
|
|
50
|
+
exports.deleteTelemetryForJob = deleteTelemetryForJob;
|
|
51
|
+
exports.getJobsWithTelemetry = getJobsWithTelemetry;
|
|
52
|
+
exports.hasJobTelemetry = hasJobTelemetry;
|
|
53
|
+
const fs_1 = __importDefault(require("fs"));
|
|
54
|
+
const path_1 = __importDefault(require("path"));
|
|
55
|
+
const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
|
|
56
|
+
const secure_fs_1 = require("./util/secure-fs");
|
|
57
|
+
const MIGRATIONS = [
|
|
58
|
+
// Migration 1: initial schema
|
|
59
|
+
(db) => {
|
|
60
|
+
db.exec(`
|
|
61
|
+
CREATE TABLE IF NOT EXISTS schema_migrations (
|
|
62
|
+
version INTEGER PRIMARY KEY,
|
|
63
|
+
applied_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
CREATE TABLE IF NOT EXISTS jobs (
|
|
67
|
+
id TEXT PRIMARY KEY,
|
|
68
|
+
command TEXT NOT NULL,
|
|
69
|
+
started_at TEXT NOT NULL,
|
|
70
|
+
finished_at TEXT,
|
|
71
|
+
status TEXT NOT NULL DEFAULT 'running',
|
|
72
|
+
exit_code INTEGER,
|
|
73
|
+
tokens_in INTEGER,
|
|
74
|
+
tokens_out INTEGER,
|
|
75
|
+
tokens_cache_read INTEGER,
|
|
76
|
+
tokens_cache_create INTEGER,
|
|
77
|
+
total_cost_usd REAL,
|
|
78
|
+
num_turns INTEGER,
|
|
79
|
+
model TEXT,
|
|
80
|
+
duration_ms INTEGER,
|
|
81
|
+
duration_api_ms INTEGER,
|
|
82
|
+
session_id TEXT
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
CREATE INDEX IF NOT EXISTS idx_jobs_started_at ON jobs(started_at);
|
|
86
|
+
CREATE INDEX IF NOT EXISTS idx_jobs_status ON jobs(status);
|
|
87
|
+
|
|
88
|
+
CREATE TABLE IF NOT EXISTS events (
|
|
89
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
90
|
+
job_id TEXT NOT NULL REFERENCES jobs(id) ON DELETE CASCADE,
|
|
91
|
+
seq INTEGER NOT NULL,
|
|
92
|
+
event_type TEXT NOT NULL,
|
|
93
|
+
source TEXT,
|
|
94
|
+
payload TEXT NOT NULL,
|
|
95
|
+
timestamp TEXT NOT NULL DEFAULT (datetime('now'))
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
CREATE INDEX IF NOT EXISTS idx_events_job_id ON events(job_id);
|
|
99
|
+
|
|
100
|
+
CREATE TABLE IF NOT EXISTS job_phases (
|
|
101
|
+
job_id TEXT NOT NULL REFERENCES jobs(id) ON DELETE CASCADE,
|
|
102
|
+
phase TEXT NOT NULL,
|
|
103
|
+
state TEXT NOT NULL,
|
|
104
|
+
updated_at TEXT NOT NULL,
|
|
105
|
+
PRIMARY KEY (job_id, phase)
|
|
106
|
+
);
|
|
107
|
+
`);
|
|
108
|
+
},
|
|
109
|
+
// Migration 2: add queue_position column to jobs
|
|
110
|
+
(db) => {
|
|
111
|
+
db.exec(`
|
|
112
|
+
ALTER TABLE jobs ADD COLUMN queue_position INTEGER;
|
|
113
|
+
`);
|
|
114
|
+
},
|
|
115
|
+
// Migration 3: add queue_state table for persisting queue config (e.g., paused)
|
|
116
|
+
(db) => {
|
|
117
|
+
db.exec(`
|
|
118
|
+
CREATE TABLE IF NOT EXISTS queue_state (
|
|
119
|
+
key TEXT PRIMARY KEY,
|
|
120
|
+
value TEXT NOT NULL
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
INSERT OR IGNORE INTO queue_state (key, value) VALUES ('paused', 'false');
|
|
124
|
+
`);
|
|
125
|
+
},
|
|
126
|
+
// Migration 4: chat conversations and messages
|
|
127
|
+
(db) => {
|
|
128
|
+
db.exec(`
|
|
129
|
+
CREATE TABLE IF NOT EXISTS chat_conversations (
|
|
130
|
+
id TEXT PRIMARY KEY,
|
|
131
|
+
title TEXT,
|
|
132
|
+
model TEXT NOT NULL DEFAULT 'sonnet',
|
|
133
|
+
session_id TEXT,
|
|
134
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
135
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
CREATE TABLE IF NOT EXISTS chat_messages (
|
|
139
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
140
|
+
conversation_id TEXT NOT NULL REFERENCES chat_conversations(id) ON DELETE CASCADE,
|
|
141
|
+
role TEXT NOT NULL CHECK(role IN ('user', 'assistant')),
|
|
142
|
+
content TEXT NOT NULL,
|
|
143
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
CREATE INDEX IF NOT EXISTS idx_chat_messages_conv ON chat_messages(conversation_id);
|
|
147
|
+
`);
|
|
148
|
+
},
|
|
149
|
+
// Migration 5: proposals table
|
|
150
|
+
(db) => {
|
|
151
|
+
db.exec(`
|
|
152
|
+
CREATE TABLE IF NOT EXISTS proposals (
|
|
153
|
+
id TEXT PRIMARY KEY,
|
|
154
|
+
idea TEXT NOT NULL,
|
|
155
|
+
session_id TEXT,
|
|
156
|
+
status TEXT NOT NULL DEFAULT 'input',
|
|
157
|
+
result_markdown TEXT,
|
|
158
|
+
issue_url TEXT,
|
|
159
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
160
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
161
|
+
);
|
|
162
|
+
CREATE INDEX IF NOT EXISTS idx_proposals_status ON proposals(status);
|
|
163
|
+
CREATE INDEX IF NOT EXISTS idx_proposals_created_at ON proposals(created_at);
|
|
164
|
+
`);
|
|
165
|
+
},
|
|
166
|
+
// Migration 6: job templates
|
|
167
|
+
(db) => {
|
|
168
|
+
db.exec(`
|
|
169
|
+
CREATE TABLE IF NOT EXISTS job_templates (
|
|
170
|
+
id TEXT NOT NULL PRIMARY KEY,
|
|
171
|
+
name TEXT NOT NULL UNIQUE,
|
|
172
|
+
description TEXT,
|
|
173
|
+
commands TEXT NOT NULL,
|
|
174
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
175
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
176
|
+
);
|
|
177
|
+
CREATE INDEX IF NOT EXISTS idx_job_templates_created_at ON job_templates(created_at);
|
|
178
|
+
`);
|
|
179
|
+
},
|
|
180
|
+
// Migration 7: add priority column to jobs
|
|
181
|
+
(db) => {
|
|
182
|
+
db.exec(`
|
|
183
|
+
ALTER TABLE jobs ADD COLUMN priority TEXT NOT NULL DEFAULT 'normal';
|
|
184
|
+
`);
|
|
185
|
+
},
|
|
186
|
+
// Migration 8: job dependencies and pipelines
|
|
187
|
+
(db) => {
|
|
188
|
+
db.exec(`
|
|
189
|
+
ALTER TABLE jobs ADD COLUMN depends_on_job_id TEXT REFERENCES jobs(id);
|
|
190
|
+
ALTER TABLE jobs ADD COLUMN pipeline_id TEXT;
|
|
191
|
+
ALTER TABLE jobs ADD COLUMN skip_reason TEXT;
|
|
192
|
+
CREATE INDEX IF NOT EXISTS idx_jobs_depends_on ON jobs(depends_on_job_id);
|
|
193
|
+
CREATE INDEX IF NOT EXISTS idx_jobs_pipeline_id ON jobs(pipeline_id);
|
|
194
|
+
`);
|
|
195
|
+
},
|
|
196
|
+
// Migration 9: rails table for Rails board job integration
|
|
197
|
+
(db) => {
|
|
198
|
+
db.exec(`
|
|
199
|
+
CREATE TABLE IF NOT EXISTS rails (
|
|
200
|
+
rail_index INTEGER NOT NULL,
|
|
201
|
+
ticket_id INTEGER NOT NULL,
|
|
202
|
+
position INTEGER NOT NULL,
|
|
203
|
+
mode TEXT NOT NULL DEFAULT 'implement',
|
|
204
|
+
PRIMARY KEY (rail_index, ticket_id)
|
|
205
|
+
);
|
|
206
|
+
CREATE INDEX IF NOT EXISTS idx_rails_rail_index ON rails(rail_index);
|
|
207
|
+
`);
|
|
208
|
+
},
|
|
209
|
+
// Migration 10: pipeline telemetry blob and summary tables.
|
|
210
|
+
// The pipelineTelemetryEnabled flag reuses the existing queue_state key-value
|
|
211
|
+
// store (key = 'config.pipeline_telemetry_enabled') so no schema change needed
|
|
212
|
+
// for settings; only the raw-data tables are new.
|
|
213
|
+
(db) => {
|
|
214
|
+
db.exec(`
|
|
215
|
+
CREATE TABLE IF NOT EXISTS telemetry_blobs (
|
|
216
|
+
jobId TEXT PRIMARY KEY,
|
|
217
|
+
path TEXT,
|
|
218
|
+
byteSize INTEGER NOT NULL DEFAULT 0,
|
|
219
|
+
startedAt INTEGER,
|
|
220
|
+
endedAt INTEGER,
|
|
221
|
+
state TEXT NOT NULL DEFAULT 'active'
|
|
222
|
+
CHECK(state IN ('active','compacted','expired'))
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
CREATE TABLE IF NOT EXISTS telemetry_summaries (
|
|
226
|
+
jobId TEXT NOT NULL,
|
|
227
|
+
phase TEXT NOT NULL,
|
|
228
|
+
durationMs INTEGER,
|
|
229
|
+
tokensInput INTEGER,
|
|
230
|
+
tokensOutput INTEGER,
|
|
231
|
+
tokensCache INTEGER,
|
|
232
|
+
toolCalls TEXT,
|
|
233
|
+
apiErrors INTEGER,
|
|
234
|
+
costUsd REAL,
|
|
235
|
+
PRIMARY KEY (jobId, phase)
|
|
236
|
+
);
|
|
237
|
+
`);
|
|
238
|
+
},
|
|
239
|
+
// Migration 11: agent profiles — per-rail profile snapshots, custom agent
|
|
240
|
+
// version history, and sandboxed "test agent" run records. These back the
|
|
241
|
+
// Agents section (profiles + studio) added by add-agents-profiles.
|
|
242
|
+
(db) => {
|
|
243
|
+
db.exec(`
|
|
244
|
+
CREATE TABLE IF NOT EXISTS job_profiles (
|
|
245
|
+
job_id TEXT PRIMARY KEY,
|
|
246
|
+
profile_name TEXT NOT NULL,
|
|
247
|
+
profile_json TEXT NOT NULL,
|
|
248
|
+
created_at INTEGER NOT NULL
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
CREATE INDEX IF NOT EXISTS idx_job_profiles_name ON job_profiles(profile_name);
|
|
252
|
+
|
|
253
|
+
CREATE TABLE IF NOT EXISTS agent_versions (
|
|
254
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
255
|
+
agent_name TEXT NOT NULL,
|
|
256
|
+
version INTEGER NOT NULL,
|
|
257
|
+
body TEXT NOT NULL,
|
|
258
|
+
created_at INTEGER NOT NULL,
|
|
259
|
+
UNIQUE (agent_name, version)
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
CREATE INDEX IF NOT EXISTS idx_agent_versions_name ON agent_versions(agent_name);
|
|
263
|
+
|
|
264
|
+
CREATE TABLE IF NOT EXISTS agent_tests (
|
|
265
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
266
|
+
agent_name TEXT NOT NULL,
|
|
267
|
+
draft_hash TEXT NOT NULL,
|
|
268
|
+
sample_task_id TEXT,
|
|
269
|
+
tokens INTEGER,
|
|
270
|
+
duration_ms INTEGER,
|
|
271
|
+
output TEXT,
|
|
272
|
+
created_at INTEGER NOT NULL
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
CREATE INDEX IF NOT EXISTS idx_agent_tests_name ON agent_tests(agent_name);
|
|
276
|
+
`);
|
|
277
|
+
},
|
|
278
|
+
// Migration 12: remember per-rail agent profile selection across launches.
|
|
279
|
+
(db) => {
|
|
280
|
+
try {
|
|
281
|
+
db.exec(`ALTER TABLE rails ADD COLUMN profile_name TEXT`);
|
|
282
|
+
}
|
|
283
|
+
catch {
|
|
284
|
+
// Column may already exist (partially-migrated DB); no-op.
|
|
285
|
+
}
|
|
286
|
+
},
|
|
287
|
+
// Migration 13: agent_refine_sessions — in-flight AI Edit sessions for
|
|
288
|
+
// custom agents. Distinct from agent_versions (which is committed history);
|
|
289
|
+
// rows here are drafts in progress that may or may not be applied.
|
|
290
|
+
(db) => {
|
|
291
|
+
db.exec(`
|
|
292
|
+
CREATE TABLE IF NOT EXISTS agent_refine_sessions (
|
|
293
|
+
id TEXT PRIMARY KEY,
|
|
294
|
+
agent_id TEXT NOT NULL,
|
|
295
|
+
session_id TEXT,
|
|
296
|
+
base_version INTEGER NOT NULL,
|
|
297
|
+
base_body_hash TEXT NOT NULL,
|
|
298
|
+
draft_body TEXT,
|
|
299
|
+
history_json TEXT NOT NULL DEFAULT '[]',
|
|
300
|
+
phase TEXT NOT NULL DEFAULT 'idle',
|
|
301
|
+
status TEXT NOT NULL DEFAULT 'idle',
|
|
302
|
+
auto_test INTEGER NOT NULL DEFAULT 1,
|
|
303
|
+
last_test_at INTEGER,
|
|
304
|
+
last_test_hash TEXT,
|
|
305
|
+
created_at INTEGER NOT NULL,
|
|
306
|
+
updated_at INTEGER NOT NULL
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
CREATE INDEX IF NOT EXISTS idx_agent_refine_sessions_agent
|
|
310
|
+
ON agent_refine_sessions(agent_id, status);
|
|
311
|
+
CREATE INDEX IF NOT EXISTS idx_agent_refine_sessions_updated
|
|
312
|
+
ON agent_refine_sessions(updated_at);
|
|
313
|
+
`);
|
|
314
|
+
},
|
|
315
|
+
// Migration 14: terminal_settings_override — per-project key/value override
|
|
316
|
+
// for app-wide terminal settings. Absence of a row means "inherit app default".
|
|
317
|
+
(db) => {
|
|
318
|
+
db.exec(`
|
|
319
|
+
CREATE TABLE IF NOT EXISTS terminal_settings_override (
|
|
320
|
+
key TEXT PRIMARY KEY,
|
|
321
|
+
value TEXT NOT NULL
|
|
322
|
+
);
|
|
323
|
+
`);
|
|
324
|
+
},
|
|
325
|
+
// Migration 15: terminal_command_marks — per-session record of completed
|
|
326
|
+
// commands derived from OSC 133 prompt marks. FIFO-capped at 1000 rows per
|
|
327
|
+
// session by the marks store.
|
|
328
|
+
(db) => {
|
|
329
|
+
db.exec(`
|
|
330
|
+
CREATE TABLE IF NOT EXISTS terminal_command_marks (
|
|
331
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
332
|
+
session_id TEXT NOT NULL,
|
|
333
|
+
started_at INTEGER NOT NULL,
|
|
334
|
+
finished_at INTEGER,
|
|
335
|
+
exit_code INTEGER,
|
|
336
|
+
command TEXT,
|
|
337
|
+
cwd TEXT
|
|
338
|
+
);
|
|
339
|
+
|
|
340
|
+
CREATE INDEX IF NOT EXISTS idx_terminal_marks_session_started
|
|
341
|
+
ON terminal_command_marks(session_id, started_at);
|
|
342
|
+
`);
|
|
343
|
+
},
|
|
344
|
+
// Migration 16: ai_invocations — unified per-project AI CLI invocation
|
|
345
|
+
// tracking across surfaces (job, quick-spec, explore-spec, ai-edit).
|
|
346
|
+
(db) => {
|
|
347
|
+
db.exec(`
|
|
348
|
+
CREATE TABLE IF NOT EXISTS ai_invocations (
|
|
349
|
+
id TEXT PRIMARY KEY,
|
|
350
|
+
project_id TEXT NOT NULL,
|
|
351
|
+
surface TEXT NOT NULL,
|
|
352
|
+
surface_ref_id TEXT,
|
|
353
|
+
ticket_id INTEGER,
|
|
354
|
+
conversation_id TEXT,
|
|
355
|
+
model TEXT,
|
|
356
|
+
status TEXT NOT NULL,
|
|
357
|
+
started_at TEXT NOT NULL,
|
|
358
|
+
finished_at TEXT,
|
|
359
|
+
duration_ms INTEGER,
|
|
360
|
+
duration_api_ms INTEGER,
|
|
361
|
+
tokens_in INTEGER,
|
|
362
|
+
tokens_out INTEGER,
|
|
363
|
+
tokens_cache_read INTEGER,
|
|
364
|
+
tokens_cache_create INTEGER,
|
|
365
|
+
total_cost_usd REAL,
|
|
366
|
+
num_turns INTEGER,
|
|
367
|
+
session_id TEXT,
|
|
368
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
369
|
+
);
|
|
370
|
+
|
|
371
|
+
CREATE INDEX IF NOT EXISTS idx_ai_inv_project_started
|
|
372
|
+
ON ai_invocations(project_id, started_at DESC);
|
|
373
|
+
CREATE INDEX IF NOT EXISTS idx_ai_inv_project_surface
|
|
374
|
+
ON ai_invocations(project_id, surface);
|
|
375
|
+
CREATE INDEX IF NOT EXISTS idx_ai_inv_project_ticket
|
|
376
|
+
ON ai_invocations(project_id, ticket_id) WHERE ticket_id IS NOT NULL;
|
|
377
|
+
`);
|
|
378
|
+
},
|
|
379
|
+
// Migration 17: chat_conversations.kind — distinguishes Explore conversations
|
|
380
|
+
// (kind='explore') from sidebar chat (kind='sidebar'). Capture for ai_invocations
|
|
381
|
+
// is gated on kind='explore'.
|
|
382
|
+
(db) => {
|
|
383
|
+
db.exec(`
|
|
384
|
+
ALTER TABLE chat_conversations ADD COLUMN kind TEXT NOT NULL DEFAULT 'sidebar';
|
|
385
|
+
`);
|
|
386
|
+
},
|
|
387
|
+
// Migration 18: chat_conversations.context_scope — per-conversation JSON
|
|
388
|
+
// freezing the Add Spec context scope at creation time. NULL means "legacy
|
|
389
|
+
// behavior" so existing rows behave unchanged.
|
|
390
|
+
//
|
|
391
|
+
// Idempotent: a parallel WIP branch shipped this column under migration #20,
|
|
392
|
+
// so on machines where it already exists we swallow the duplicate-column
|
|
393
|
+
// error rather than crash on a re-run.
|
|
394
|
+
(db) => {
|
|
395
|
+
try {
|
|
396
|
+
db.exec(`ALTER TABLE chat_conversations ADD COLUMN context_scope TEXT;`);
|
|
397
|
+
}
|
|
398
|
+
catch (err) {
|
|
399
|
+
const msg = err.message ?? '';
|
|
400
|
+
if (!/duplicate column name/i.test(msg))
|
|
401
|
+
throw err;
|
|
402
|
+
}
|
|
403
|
+
},
|
|
404
|
+
// Migration 19: ai_invocations.provider — provider id stamped at insert.
|
|
405
|
+
// Existing rows backfill to 'claude' since pre-migration that was the only
|
|
406
|
+
// path. New rows MUST be populated from the resolved adapter's id (see
|
|
407
|
+
// openspec/changes/add-multi-provider-support/specs/project-spending/spec.md).
|
|
408
|
+
//
|
|
409
|
+
// Idempotent: same dual-WIP concern — multi-provider branch originally
|
|
410
|
+
// numbered this #18, so on machines that ran the pre-merge multi-provider
|
|
411
|
+
// build the column already exists.
|
|
412
|
+
(db) => {
|
|
413
|
+
try {
|
|
414
|
+
db.exec(`ALTER TABLE ai_invocations ADD COLUMN provider TEXT;`);
|
|
415
|
+
}
|
|
416
|
+
catch (err) {
|
|
417
|
+
const msg = err.message ?? '';
|
|
418
|
+
if (!/duplicate column name/i.test(msg))
|
|
419
|
+
throw err;
|
|
420
|
+
}
|
|
421
|
+
db.exec(`UPDATE ai_invocations SET provider = 'claude' WHERE provider IS NULL;`);
|
|
422
|
+
db.exec(`
|
|
423
|
+
CREATE INDEX IF NOT EXISTS idx_ai_inv_project_provider
|
|
424
|
+
ON ai_invocations(project_id, provider);
|
|
425
|
+
`);
|
|
426
|
+
},
|
|
427
|
+
// Migration 20: ai_invocations.total_cost_usd_estimated — 1 when the cost
|
|
428
|
+
// came from server/pricing.ts (estimated fallback for non-native-cost
|
|
429
|
+
// providers); 0 when authoritative from the provider's terminal event.
|
|
430
|
+
//
|
|
431
|
+
// Idempotent for the same reason as #18/#19.
|
|
432
|
+
(db) => {
|
|
433
|
+
try {
|
|
434
|
+
db.exec(`
|
|
435
|
+
ALTER TABLE ai_invocations
|
|
436
|
+
ADD COLUMN total_cost_usd_estimated INTEGER NOT NULL DEFAULT 0;
|
|
437
|
+
`);
|
|
438
|
+
}
|
|
439
|
+
catch (err) {
|
|
440
|
+
const msg = err.message ?? '';
|
|
441
|
+
if (!/duplicate column name/i.test(msg))
|
|
442
|
+
throw err;
|
|
443
|
+
}
|
|
444
|
+
},
|
|
445
|
+
// Migration 21: self-heal `ai_invocations.provider` and
|
|
446
|
+
// `ai_invocations.total_cost_usd_estimated` for projects whose
|
|
447
|
+
// `schema_migrations` table marked versions 19 / 20 as applied without the
|
|
448
|
+
// corresponding ALTER actually running (parallel WIP branches reshuffled
|
|
449
|
+
// migration indices during development, leaving some on-disk DBs with the
|
|
450
|
+
// applied row but no column). Uses `PRAGMA table_info` so we only ALTER
|
|
451
|
+
// when the column is genuinely missing — safe to re-run.
|
|
452
|
+
(db) => {
|
|
453
|
+
const cols = new Set(db.prepare(`PRAGMA table_info(ai_invocations)`).all()
|
|
454
|
+
.map((r) => r.name));
|
|
455
|
+
if (!cols.has('provider')) {
|
|
456
|
+
db.exec(`ALTER TABLE ai_invocations ADD COLUMN provider TEXT;`);
|
|
457
|
+
db.exec(`UPDATE ai_invocations SET provider = 'claude' WHERE provider IS NULL;`);
|
|
458
|
+
db.exec(`
|
|
459
|
+
CREATE INDEX IF NOT EXISTS idx_ai_inv_project_provider
|
|
460
|
+
ON ai_invocations(project_id, provider);
|
|
461
|
+
`);
|
|
462
|
+
}
|
|
463
|
+
if (!cols.has('total_cost_usd_estimated')) {
|
|
464
|
+
db.exec(`
|
|
465
|
+
ALTER TABLE ai_invocations
|
|
466
|
+
ADD COLUMN total_cost_usd_estimated INTEGER NOT NULL DEFAULT 0;
|
|
467
|
+
`);
|
|
468
|
+
}
|
|
469
|
+
},
|
|
470
|
+
// Migration 22: file_provenance — per-project file ⇄ ticket tracking,
|
|
471
|
+
// populated by the QueueManager post-job hook and consumed by the Code
|
|
472
|
+
// Explorer router + TicketDetailModal.
|
|
473
|
+
(db) => {
|
|
474
|
+
db.exec(`
|
|
475
|
+
CREATE TABLE IF NOT EXISTS file_provenance (
|
|
476
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
477
|
+
file_path TEXT NOT NULL,
|
|
478
|
+
ticket_id INTEGER,
|
|
479
|
+
job_id TEXT,
|
|
480
|
+
kind TEXT NOT NULL CHECK(kind IN ('created','modified','deleted')),
|
|
481
|
+
at INTEGER NOT NULL
|
|
482
|
+
);
|
|
483
|
+
CREATE INDEX IF NOT EXISTS idx_fp_path ON file_provenance(file_path);
|
|
484
|
+
CREATE INDEX IF NOT EXISTS idx_fp_ticket ON file_provenance(ticket_id);
|
|
485
|
+
CREATE INDEX IF NOT EXISTS idx_fp_at ON file_provenance(at DESC);
|
|
486
|
+
`);
|
|
487
|
+
},
|
|
488
|
+
// Migration 23: optional per-job file patch storage for Code Explorer
|
|
489
|
+
// provenance. Older provenance rows remain valid without a patch.
|
|
490
|
+
(db) => {
|
|
491
|
+
db.exec(`
|
|
492
|
+
CREATE TABLE IF NOT EXISTS file_provenance_diffs (
|
|
493
|
+
provenance_id INTEGER PRIMARY KEY REFERENCES file_provenance(id) ON DELETE CASCADE,
|
|
494
|
+
patch TEXT NOT NULL,
|
|
495
|
+
truncated INTEGER NOT NULL DEFAULT 0
|
|
496
|
+
);
|
|
497
|
+
`);
|
|
498
|
+
},
|
|
499
|
+
// Migration 24: chat_conversations.provider — per-conversation AI engine for
|
|
500
|
+
// multi-provider projects. NULL means "fall back to the project's primary
|
|
501
|
+
// provider" (single-provider projects never set it, so behaviour is
|
|
502
|
+
// unchanged). Set at conversation creation from the Add Spec AI Engine
|
|
503
|
+
// selector; resume turns reuse it so the right CLI binary is spawned.
|
|
504
|
+
(db) => {
|
|
505
|
+
db.exec(`ALTER TABLE chat_conversations ADD COLUMN provider TEXT;`);
|
|
506
|
+
},
|
|
507
|
+
// Migration 25: rails.ai_engine — per-rail AI engine override for
|
|
508
|
+
// multi-provider projects. NULL means "use the project's primary provider".
|
|
509
|
+
// Stored on every rail row alongside profile_name; getRail reads the first
|
|
510
|
+
// row's value.
|
|
511
|
+
(db) => {
|
|
512
|
+
db.exec(`ALTER TABLE rails ADD COLUMN ai_engine TEXT;`);
|
|
513
|
+
},
|
|
514
|
+
// Migration 26: self-heal the multi-provider columns. An earlier WIP of the
|
|
515
|
+
// multi-provider feature consumed migration versions 24 and 25 on some
|
|
516
|
+
// databases with DIFFERENT meaning — those DBs already record v24/v25 so
|
|
517
|
+
// Migrations 24/25 above are skipped, leaving `rails.ai_engine` (and possibly
|
|
518
|
+
// `chat_conversations.provider`) missing. This higher-numbered migration is
|
|
519
|
+
// guarded by column checks: a no-op on DBs where the columns already exist,
|
|
520
|
+
// and an additive repair everywhere else. (Mirrors the #18/#19 self-heal
|
|
521
|
+
// precedent in this file.)
|
|
522
|
+
(db) => {
|
|
523
|
+
const convCols = db.prepare("PRAGMA table_info(chat_conversations)").all().map((c) => c.name);
|
|
524
|
+
if (!convCols.includes('provider')) {
|
|
525
|
+
db.exec(`ALTER TABLE chat_conversations ADD COLUMN provider TEXT;`);
|
|
526
|
+
}
|
|
527
|
+
const railCols = db.prepare("PRAGMA table_info(rails)").all().map((c) => c.name);
|
|
528
|
+
if (!railCols.includes('ai_engine')) {
|
|
529
|
+
db.exec(`ALTER TABLE rails ADD COLUMN ai_engine TEXT;`);
|
|
530
|
+
}
|
|
531
|
+
},
|
|
532
|
+
// Migration 27: jobs.total_cost_usd_estimated — 1 when jobs.total_cost_usd
|
|
533
|
+
// came from server/pricing.ts (estimated fallback for non-native-cost
|
|
534
|
+
// providers like codex); 0 when authoritative from the provider's terminal
|
|
535
|
+
// event. Mirrors the ai_invocations column (migration 20) so the app
|
|
536
|
+
// dashboard, budget enforcement, and webhook can distinguish a rate-card
|
|
537
|
+
// estimate from a provider-billed figure. Additive + idempotent.
|
|
538
|
+
(db) => {
|
|
539
|
+
const cols = db.prepare(`PRAGMA table_info(jobs)`).all().map((r) => r.name);
|
|
540
|
+
if (!cols.includes('total_cost_usd_estimated')) {
|
|
541
|
+
db.exec(`
|
|
542
|
+
ALTER TABLE jobs
|
|
543
|
+
ADD COLUMN total_cost_usd_estimated INTEGER NOT NULL DEFAULT 0;
|
|
544
|
+
`);
|
|
545
|
+
}
|
|
546
|
+
},
|
|
547
|
+
// Migration 28: rail_meta — per-rail display name, keyed by rail_index.
|
|
548
|
+
// The `rails` table stores name-less ticket rows (and has NO rows for an
|
|
549
|
+
// empty rail), so a rail's user-given name can't live there — a renamed but
|
|
550
|
+
// empty rail would lose its name. rail_meta is a separate, ticket-independent
|
|
551
|
+
// store so every rail (0/1/2) keeps its name regardless of assignments.
|
|
552
|
+
// NULL name = client falls back to the default "Rail N" label. This backs the
|
|
553
|
+
// desktop ⇄ mobile rail-name sync (broadcast via rail.updated).
|
|
554
|
+
(db) => {
|
|
555
|
+
db.exec(`
|
|
556
|
+
CREATE TABLE IF NOT EXISTS rail_meta (
|
|
557
|
+
rail_index INTEGER PRIMARY KEY,
|
|
558
|
+
name TEXT
|
|
559
|
+
);
|
|
560
|
+
`);
|
|
561
|
+
},
|
|
562
|
+
];
|
|
563
|
+
function applyMigrations(db) {
|
|
564
|
+
// Ensure the migrations table exists (migration 1 creates it, but we need
|
|
565
|
+
// it before we can read from it)
|
|
566
|
+
db.exec(`
|
|
567
|
+
CREATE TABLE IF NOT EXISTS schema_migrations (
|
|
568
|
+
version INTEGER PRIMARY KEY,
|
|
569
|
+
applied_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
570
|
+
)
|
|
571
|
+
`);
|
|
572
|
+
const appliedVersions = new Set(db.prepare('SELECT version FROM schema_migrations').all()
|
|
573
|
+
.map((r) => r.version));
|
|
574
|
+
for (let i = 0; i < MIGRATIONS.length; i++) {
|
|
575
|
+
const version = i + 1;
|
|
576
|
+
if (!appliedVersions.has(version)) {
|
|
577
|
+
// M8: run each migration body and its version INSERT atomically. SQLite DDL
|
|
578
|
+
// is transactional, so a crash/failure mid-migration now rolls back the
|
|
579
|
+
// whole body instead of leaving a half-applied schema with the version
|
|
580
|
+
// unrecorded — which under the old code re-ran a bare `ALTER TABLE ADD
|
|
581
|
+
// COLUMN` on next startup and bricked it forever with 'duplicate column
|
|
582
|
+
// name'. With this, a failed migration leaves nothing applied and re-runs
|
|
583
|
+
// cleanly. (Pre-existing half-applied DBs are contained by the per-project
|
|
584
|
+
// load isolation in project-registry.ts — one bad DB no longer kills the app.)
|
|
585
|
+
const tx = db.transaction(() => {
|
|
586
|
+
MIGRATIONS[i](db);
|
|
587
|
+
db.prepare('INSERT OR IGNORE INTO schema_migrations (version) VALUES (?)').run(version);
|
|
588
|
+
});
|
|
589
|
+
tx();
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
// ─── Public API ──────────────────────────────────────────────────────────────
|
|
594
|
+
function initDb(dbPath) {
|
|
595
|
+
if (dbPath !== ':memory:') {
|
|
596
|
+
const dir = path_1.default.dirname(dbPath);
|
|
597
|
+
fs_1.default.mkdirSync(dir, { recursive: true });
|
|
598
|
+
(0, secure_fs_1.secureDir)(dir); // H-13: owner-only data dir
|
|
599
|
+
}
|
|
600
|
+
const db = new better_sqlite3_1.default(dbPath);
|
|
601
|
+
db.pragma('journal_mode = WAL');
|
|
602
|
+
db.pragma('foreign_keys = ON');
|
|
603
|
+
applyMigrations(db);
|
|
604
|
+
// H-13: restrict the db + its WAL sidecars to 0600 (jobs.sqlite holds chat
|
|
605
|
+
// transcripts and verbatim terminal command history). After migrations the
|
|
606
|
+
// WAL/SHM files exist, so this covers them too.
|
|
607
|
+
(0, secure_fs_1.secureDbFile)(dbPath);
|
|
608
|
+
// Orphan sweep: mark any running jobs as failed on startup
|
|
609
|
+
db.prepare("UPDATE jobs SET status = 'failed', finished_at = ? WHERE status = 'running'").run(new Date().toISOString());
|
|
610
|
+
// Orphan sweep: cancel any in-flight proposals from a previous server session
|
|
611
|
+
db.prepare("UPDATE proposals SET status = 'cancelled', updated_at = ? WHERE status IN ('exploring', 'refining')").run(new Date().toISOString());
|
|
612
|
+
return db;
|
|
613
|
+
}
|
|
614
|
+
function createJob(db, job) {
|
|
615
|
+
// INSERT OR IGNORE handles the case where the job row already exists (restored from DB
|
|
616
|
+
// after server restart). The UPDATE that follows always sets status and started_at.
|
|
617
|
+
db.prepare('INSERT OR IGNORE INTO jobs (id, command, started_at, status, priority, depends_on_job_id, pipeline_id) VALUES (?, ?, ?, ?, ?, ?, ?)').run(job.id, job.command, job.started_at, 'running', job.priority ?? 'normal', job.depends_on_job_id ?? null, job.pipeline_id ?? null);
|
|
618
|
+
db.prepare('UPDATE jobs SET status = ?, started_at = ? WHERE id = ?').run('running', job.started_at, job.id);
|
|
619
|
+
}
|
|
620
|
+
function finishJob(db, jobId, result) {
|
|
621
|
+
db.prepare(`
|
|
622
|
+
UPDATE jobs SET
|
|
623
|
+
status = ?,
|
|
624
|
+
exit_code = ?,
|
|
625
|
+
finished_at = ?,
|
|
626
|
+
tokens_in = ?,
|
|
627
|
+
tokens_out = ?,
|
|
628
|
+
tokens_cache_read = ?,
|
|
629
|
+
tokens_cache_create = ?,
|
|
630
|
+
total_cost_usd = ?,
|
|
631
|
+
total_cost_usd_estimated = ?,
|
|
632
|
+
num_turns = ?,
|
|
633
|
+
model = ?,
|
|
634
|
+
duration_ms = ?,
|
|
635
|
+
duration_api_ms = ?,
|
|
636
|
+
session_id = ?
|
|
637
|
+
WHERE id = ?
|
|
638
|
+
`).run(result.status, result.exit_code, new Date().toISOString(), result.tokens_in ?? null, result.tokens_out ?? null, result.tokens_cache_read ?? null, result.tokens_cache_create ?? null, result.total_cost_usd ?? null, result.total_cost_usd_estimated ? 1 : 0, result.num_turns ?? null, result.model ?? null, result.duration_ms ?? null, result.duration_api_ms ?? null, result.session_id ?? null, jobId);
|
|
639
|
+
}
|
|
640
|
+
function appendEvent(db, jobId, seq, event) {
|
|
641
|
+
db.prepare('INSERT INTO events (job_id, seq, event_type, source, payload) VALUES (?, ?, ?, ?, ?)').run(jobId, seq, event.event_type, event.source ?? null, event.payload);
|
|
642
|
+
}
|
|
643
|
+
function upsertPhase(db, jobId, phase, state) {
|
|
644
|
+
db.prepare('INSERT OR REPLACE INTO job_phases (job_id, phase, state, updated_at) VALUES (?, ?, ?, ?)').run(jobId, phase, state, new Date().toISOString());
|
|
645
|
+
}
|
|
646
|
+
function listJobs(db, opts) {
|
|
647
|
+
const limit = Math.min(opts.limit ?? 50, 200);
|
|
648
|
+
const offset = opts.offset ?? 0;
|
|
649
|
+
const conditions = [];
|
|
650
|
+
const params = [];
|
|
651
|
+
if (opts.status) {
|
|
652
|
+
conditions.push('status = ?');
|
|
653
|
+
params.push(opts.status);
|
|
654
|
+
}
|
|
655
|
+
if (opts.from) {
|
|
656
|
+
conditions.push('started_at >= ?');
|
|
657
|
+
params.push(opts.from);
|
|
658
|
+
}
|
|
659
|
+
if (opts.to) {
|
|
660
|
+
conditions.push('started_at <= ?');
|
|
661
|
+
params.push(opts.to);
|
|
662
|
+
}
|
|
663
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
|
|
664
|
+
const countRow = db
|
|
665
|
+
.prepare(`SELECT COUNT(*) as count FROM jobs ${where}`)
|
|
666
|
+
.get(...params);
|
|
667
|
+
const jobs = db
|
|
668
|
+
.prepare(`SELECT jobs.*, jp.profile_name AS profile_name
|
|
669
|
+
FROM jobs LEFT JOIN job_profiles jp ON jp.job_id = jobs.id
|
|
670
|
+
${where}
|
|
671
|
+
ORDER BY started_at DESC LIMIT ? OFFSET ?`)
|
|
672
|
+
.all(...params, limit, offset);
|
|
673
|
+
return { jobs, total: countRow.count };
|
|
674
|
+
}
|
|
675
|
+
function getJob(db, jobId) {
|
|
676
|
+
return db
|
|
677
|
+
.prepare('SELECT * FROM jobs WHERE id = ?')
|
|
678
|
+
.get(jobId);
|
|
679
|
+
}
|
|
680
|
+
function getJobEvents(db, jobId) {
|
|
681
|
+
return db
|
|
682
|
+
.prepare('SELECT * FROM events WHERE job_id = ? ORDER BY seq ASC')
|
|
683
|
+
.all(jobId);
|
|
684
|
+
}
|
|
685
|
+
function deleteJob(db, jobId) {
|
|
686
|
+
// M7: jobs.depends_on_job_id REFERENCES jobs(id) with no ON DELETE action and
|
|
687
|
+
// foreign_keys=ON, so deleting a pipeline parent throws 'FOREIGN KEY
|
|
688
|
+
// constraint failed' and the job becomes undeletable from the UI. Clear inbound
|
|
689
|
+
// references first, in the same transaction as the delete, so it always
|
|
690
|
+
// succeeds (children keep running; they just lose the now-irrelevant pointer).
|
|
691
|
+
const tx = db.transaction((id) => {
|
|
692
|
+
db.prepare('UPDATE jobs SET depends_on_job_id = NULL WHERE depends_on_job_id = ?').run(id);
|
|
693
|
+
// B41: events/job_phases cascade on the jobs FK, but telemetry_blobs/
|
|
694
|
+
// telemetry_summaries (keyed `jobId`), job_profiles and file_provenance
|
|
695
|
+
// (keyed `job_id`) have no FK — without these they accumulate forever. (The
|
|
696
|
+
// on-disk .ndjson.gz blob is reclaimed by the 7-day startup compactor.)
|
|
697
|
+
db.prepare('DELETE FROM telemetry_blobs WHERE jobId = ?').run(id);
|
|
698
|
+
db.prepare('DELETE FROM telemetry_summaries WHERE jobId = ?').run(id);
|
|
699
|
+
db.prepare('DELETE FROM job_profiles WHERE job_id = ?').run(id);
|
|
700
|
+
db.prepare('DELETE FROM file_provenance WHERE job_id = ?').run(id);
|
|
701
|
+
db.prepare('DELETE FROM jobs WHERE id = ?').run(id);
|
|
702
|
+
});
|
|
703
|
+
tx(jobId);
|
|
704
|
+
}
|
|
705
|
+
function purgeJobs(db, opts) {
|
|
706
|
+
const conditions = ["status IN ('completed', 'failed', 'canceled', 'zombie_terminated', 'skipped')"];
|
|
707
|
+
const params = [];
|
|
708
|
+
if (opts?.from) {
|
|
709
|
+
conditions.push('started_at >= ?');
|
|
710
|
+
params.push(opts.from);
|
|
711
|
+
}
|
|
712
|
+
if (opts?.to) {
|
|
713
|
+
conditions.push('started_at <= ?');
|
|
714
|
+
params.push(opts.to);
|
|
715
|
+
}
|
|
716
|
+
const where = conditions.join(' AND ');
|
|
717
|
+
// M6: run the whole purge atomically. Previously these statements ran without a
|
|
718
|
+
// transaction, so when the final `DELETE FROM jobs` aborted on the
|
|
719
|
+
// depends_on_job_id FK (a purged job still referenced by a non-purged one), the
|
|
720
|
+
// events/phases deletes had already committed — destroying log history while
|
|
721
|
+
// deleting zero job rows, and a misleading 500. The transaction rolls back on
|
|
722
|
+
// any failure, and NULL-ing inbound references first makes the delete succeed.
|
|
723
|
+
const tx = db.transaction(() => {
|
|
724
|
+
const sel = `SELECT id FROM jobs WHERE ${where}`;
|
|
725
|
+
db.prepare(`DELETE FROM events WHERE job_id IN (${sel})`).run(...params);
|
|
726
|
+
db.prepare(`DELETE FROM job_phases WHERE job_id IN (${sel})`).run(...params);
|
|
727
|
+
// B41: also purge the no-FK orphan tables for the same jobs.
|
|
728
|
+
db.prepare(`DELETE FROM telemetry_blobs WHERE jobId IN (${sel})`).run(...params);
|
|
729
|
+
db.prepare(`DELETE FROM telemetry_summaries WHERE jobId IN (${sel})`).run(...params);
|
|
730
|
+
db.prepare(`DELETE FROM job_profiles WHERE job_id IN (${sel})`).run(...params);
|
|
731
|
+
db.prepare(`DELETE FROM file_provenance WHERE job_id IN (${sel})`).run(...params);
|
|
732
|
+
// Clear inbound FK references from NON-purged jobs to purged jobs.
|
|
733
|
+
db.prepare(`UPDATE jobs SET depends_on_job_id = NULL WHERE depends_on_job_id IN (${sel})`).run(...params);
|
|
734
|
+
return db.prepare(`DELETE FROM jobs WHERE ${where}`).run(...params).changes;
|
|
735
|
+
});
|
|
736
|
+
return tx();
|
|
737
|
+
}
|
|
738
|
+
function getProjectActivity(db, opts) {
|
|
739
|
+
const limit = Math.min(opts.limit, 100);
|
|
740
|
+
const conditions = [];
|
|
741
|
+
const params = [];
|
|
742
|
+
if (opts.before) {
|
|
743
|
+
conditions.push('started_at < ?');
|
|
744
|
+
params.push(opts.before);
|
|
745
|
+
}
|
|
746
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
|
|
747
|
+
const jobs = db
|
|
748
|
+
.prepare(`SELECT * FROM jobs ${where} ORDER BY started_at DESC LIMIT ?`)
|
|
749
|
+
.all(...params, limit);
|
|
750
|
+
return jobs.map((j) => {
|
|
751
|
+
const isTerminal = j.status === 'completed' || j.status === 'failed' || j.status === 'canceled' || j.status === 'zombie_terminated';
|
|
752
|
+
const type = j.status === 'completed' ? 'job_completed'
|
|
753
|
+
: j.status === 'failed' ? 'job_failed'
|
|
754
|
+
: (j.status === 'canceled' || j.status === 'zombie_terminated') ? 'job_canceled'
|
|
755
|
+
: 'job_started';
|
|
756
|
+
const timestamp = isTerminal && j.finished_at ? j.finished_at : j.started_at;
|
|
757
|
+
const shortCmd = j.command.length > 60 ? j.command.slice(0, 57) + '...' : j.command;
|
|
758
|
+
const summary = type === 'job_started' ? `Job started: ${shortCmd}`
|
|
759
|
+
: type === 'job_completed' ? `Job completed: ${shortCmd}`
|
|
760
|
+
: type === 'job_failed' ? `Job failed: ${shortCmd}`
|
|
761
|
+
: j.status === 'zombie_terminated' ? `Job auto-terminated (zombie): ${shortCmd}`
|
|
762
|
+
: `Job canceled: ${shortCmd}`;
|
|
763
|
+
return {
|
|
764
|
+
id: j.id,
|
|
765
|
+
type,
|
|
766
|
+
jobId: j.id,
|
|
767
|
+
jobCommand: j.command,
|
|
768
|
+
timestamp,
|
|
769
|
+
summary,
|
|
770
|
+
costUsd: isTerminal ? (j.total_cost_usd ?? null) : null,
|
|
771
|
+
};
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
// ─── Chat DB functions ────────────────────────────────────────────────────────
|
|
775
|
+
function createConversation(db, opts) {
|
|
776
|
+
const scopeJson = opts.contextScope != null ? JSON.stringify(opts.contextScope) : null;
|
|
777
|
+
db.prepare('INSERT INTO chat_conversations (id, model, kind, context_scope, provider) VALUES (?, ?, ?, ?, ?)').run(opts.id, opts.model, opts.kind ?? 'sidebar', scopeJson, opts.provider ?? null);
|
|
778
|
+
}
|
|
779
|
+
function listConversations(db) {
|
|
780
|
+
return db.prepare('SELECT * FROM chat_conversations ORDER BY updated_at DESC').all();
|
|
781
|
+
}
|
|
782
|
+
function getConversation(db, id) {
|
|
783
|
+
return db.prepare('SELECT * FROM chat_conversations WHERE id = ?').get(id);
|
|
784
|
+
}
|
|
785
|
+
function deleteConversation(db, id) {
|
|
786
|
+
db.prepare('DELETE FROM chat_conversations WHERE id = ?').run(id);
|
|
787
|
+
}
|
|
788
|
+
function updateConversation(db, id, patch) {
|
|
789
|
+
const sets = ['updated_at = ?'];
|
|
790
|
+
const params = [new Date().toISOString()];
|
|
791
|
+
if (patch.title !== undefined) {
|
|
792
|
+
sets.push('title = ?');
|
|
793
|
+
params.push(patch.title);
|
|
794
|
+
}
|
|
795
|
+
if (patch.session_id !== undefined) {
|
|
796
|
+
sets.push('session_id = ?');
|
|
797
|
+
params.push(patch.session_id);
|
|
798
|
+
}
|
|
799
|
+
if (patch.model !== undefined) {
|
|
800
|
+
sets.push('model = ?');
|
|
801
|
+
params.push(patch.model);
|
|
802
|
+
}
|
|
803
|
+
params.push(id);
|
|
804
|
+
db.prepare(`UPDATE chat_conversations SET ${sets.join(', ')} WHERE id = ?`).run(...params);
|
|
805
|
+
}
|
|
806
|
+
function addMessage(db, msg) {
|
|
807
|
+
const result = db.prepare('INSERT INTO chat_messages (conversation_id, role, content) VALUES (?, ?, ?)').run(msg.conversation_id, msg.role, msg.content);
|
|
808
|
+
return db.prepare('SELECT * FROM chat_messages WHERE id = ?').get(Number(result.lastInsertRowid));
|
|
809
|
+
}
|
|
810
|
+
function getMessages(db, conversationId) {
|
|
811
|
+
return db.prepare('SELECT * FROM chat_messages WHERE conversation_id = ? ORDER BY id ASC').all(conversationId);
|
|
812
|
+
}
|
|
813
|
+
// ─── Proposal DB functions ────────────────────────────────────────────────────
|
|
814
|
+
function createProposal(db, opts) {
|
|
815
|
+
db.prepare('INSERT INTO proposals (id, idea, status) VALUES (?, ?, ?)').run(opts.id, opts.idea, 'input');
|
|
816
|
+
}
|
|
817
|
+
function getProposal(db, id) {
|
|
818
|
+
return db.prepare('SELECT * FROM proposals WHERE id = ?').get(id);
|
|
819
|
+
}
|
|
820
|
+
function listProposals(db, opts) {
|
|
821
|
+
const limit = Math.min(opts?.limit ?? 20, 100);
|
|
822
|
+
const offset = opts?.offset ?? 0;
|
|
823
|
+
const countRow = db
|
|
824
|
+
.prepare('SELECT COUNT(*) as count FROM proposals')
|
|
825
|
+
.get();
|
|
826
|
+
const proposals = db
|
|
827
|
+
.prepare('SELECT * FROM proposals ORDER BY created_at DESC LIMIT ? OFFSET ?')
|
|
828
|
+
.all(limit, offset);
|
|
829
|
+
return { proposals, total: countRow.count };
|
|
830
|
+
}
|
|
831
|
+
function updateProposal(db, id, patch) {
|
|
832
|
+
const sets = ['updated_at = ?'];
|
|
833
|
+
const params = [new Date().toISOString()];
|
|
834
|
+
if (patch.status !== undefined) {
|
|
835
|
+
sets.push('status = ?');
|
|
836
|
+
params.push(patch.status);
|
|
837
|
+
}
|
|
838
|
+
if (patch.session_id !== undefined) {
|
|
839
|
+
sets.push('session_id = ?');
|
|
840
|
+
params.push(patch.session_id);
|
|
841
|
+
}
|
|
842
|
+
if (patch.result_markdown !== undefined) {
|
|
843
|
+
sets.push('result_markdown = ?');
|
|
844
|
+
params.push(patch.result_markdown);
|
|
845
|
+
}
|
|
846
|
+
if (patch.issue_url !== undefined) {
|
|
847
|
+
sets.push('issue_url = ?');
|
|
848
|
+
params.push(patch.issue_url);
|
|
849
|
+
}
|
|
850
|
+
params.push(id);
|
|
851
|
+
db.prepare(`UPDATE proposals SET ${sets.join(', ')} WHERE id = ?`).run(...params);
|
|
852
|
+
}
|
|
853
|
+
function deleteProposal(db, id) {
|
|
854
|
+
db.prepare('DELETE FROM proposals WHERE id = ?').run(id);
|
|
855
|
+
}
|
|
856
|
+
function createTemplate(db, t) {
|
|
857
|
+
db.prepare('INSERT INTO job_templates (id, name, description, commands) VALUES (?, ?, ?, ?)').run(t.id, t.name, t.description ?? null, JSON.stringify(t.commands));
|
|
858
|
+
}
|
|
859
|
+
function listTemplates(db) {
|
|
860
|
+
return db.prepare('SELECT * FROM job_templates ORDER BY created_at DESC').all();
|
|
861
|
+
}
|
|
862
|
+
function getTemplate(db, id) {
|
|
863
|
+
return db.prepare('SELECT * FROM job_templates WHERE id = ?').get(id);
|
|
864
|
+
}
|
|
865
|
+
function updateTemplate(db, id, patch) {
|
|
866
|
+
const sets = ['updated_at = ?'];
|
|
867
|
+
const params = [new Date().toISOString()];
|
|
868
|
+
if (patch.name !== undefined) {
|
|
869
|
+
sets.push('name = ?');
|
|
870
|
+
params.push(patch.name);
|
|
871
|
+
}
|
|
872
|
+
if (patch.description !== undefined) {
|
|
873
|
+
sets.push('description = ?');
|
|
874
|
+
params.push(patch.description);
|
|
875
|
+
}
|
|
876
|
+
if (patch.commands !== undefined) {
|
|
877
|
+
sets.push('commands = ?');
|
|
878
|
+
params.push(JSON.stringify(patch.commands));
|
|
879
|
+
}
|
|
880
|
+
params.push(id);
|
|
881
|
+
db.prepare(`UPDATE job_templates SET ${sets.join(', ')} WHERE id = ?`).run(...params);
|
|
882
|
+
}
|
|
883
|
+
function deleteTemplate(db, id) {
|
|
884
|
+
db.prepare('DELETE FROM job_templates WHERE id = ?').run(id);
|
|
885
|
+
}
|
|
886
|
+
function skipJob(db, jobId, reason) {
|
|
887
|
+
db.prepare(`UPDATE jobs SET status = 'skipped', skip_reason = ?, finished_at = ? WHERE id = ?`).run(reason, new Date().toISOString(), jobId);
|
|
888
|
+
}
|
|
889
|
+
function getPipelineJobs(db, pipelineId) {
|
|
890
|
+
return db.prepare('SELECT * FROM jobs WHERE pipeline_id = ? ORDER BY queue_position ASC, started_at ASC').all(pipelineId);
|
|
891
|
+
}
|
|
892
|
+
function getStats(db) {
|
|
893
|
+
const today = new Date().toISOString().slice(0, 10); // YYYY-MM-DD
|
|
894
|
+
const totalRow = db.prepare(`
|
|
895
|
+
SELECT
|
|
896
|
+
COUNT(*) as totalJobs,
|
|
897
|
+
SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END) as failedJobs,
|
|
898
|
+
SUM(total_cost_usd) as totalCostUsd,
|
|
899
|
+
AVG(duration_ms) as avgDurationMs
|
|
900
|
+
FROM jobs
|
|
901
|
+
`).get();
|
|
902
|
+
const todayRow = db.prepare(`
|
|
903
|
+
SELECT
|
|
904
|
+
COUNT(*) as jobsToday,
|
|
905
|
+
SUM(total_cost_usd) as costToday
|
|
906
|
+
FROM jobs
|
|
907
|
+
WHERE strftime('%Y-%m-%d', started_at) = ?
|
|
908
|
+
`).get(today);
|
|
909
|
+
return {
|
|
910
|
+
totalJobs: totalRow.totalJobs,
|
|
911
|
+
failedJobs: totalRow.failedJobs ?? 0,
|
|
912
|
+
jobsToday: todayRow.jobsToday,
|
|
913
|
+
totalCostUsd: totalRow.totalCostUsd ?? 0,
|
|
914
|
+
costToday: todayRow.costToday ?? 0,
|
|
915
|
+
avgDurationMs: totalRow.avgDurationMs,
|
|
916
|
+
};
|
|
917
|
+
}
|
|
918
|
+
// ─── Project settings ─────────────────────────────────────────────────────────
|
|
919
|
+
/**
|
|
920
|
+
* Default pre-prompt used by Ultracode (Claude-only rails) when the project
|
|
921
|
+
* has no per-project override. Ultracode skips the OpenSpec pipeline entirely:
|
|
922
|
+
* it hands Claude the spec text plus this instruction and lets it work
|
|
923
|
+
* autonomously end-to-end.
|
|
924
|
+
*/
|
|
925
|
+
exports.DEFAULT_ULTRACODE_PRE_PROMPT = [
|
|
926
|
+
'You are operating in ULTRACODE: fully autonomous, end-to-end implementation.',
|
|
927
|
+
'Implement the following spec COMPLETELY in this repository. You have full access to the codebase and tools.',
|
|
928
|
+
'Work independently until the feature is done: write the code, the tests, update docs as needed, and make sure everything builds and the test suite passes.',
|
|
929
|
+
'Do NOT follow any structured architect/developer/reviewer pipeline — use your own judgement and the repo conventions.',
|
|
930
|
+
'Never ask for confirmation; there is no human to answer. Choose the recommended option and proceed.',
|
|
931
|
+
].join('\n');
|
|
932
|
+
function getProjectSettings(db) {
|
|
933
|
+
const telemetryRow = db.prepare(`SELECT value FROM queue_state WHERE key = 'config.pipeline_telemetry_enabled'`).get();
|
|
934
|
+
const modelRow = db.prepare(`SELECT value FROM queue_state WHERE key = 'config.orchestrator_model'`).get();
|
|
935
|
+
const prePromptRow = db.prepare(`SELECT value FROM queue_state WHERE key = 'config.pre_prompt'`).get();
|
|
936
|
+
const ultraPrePromptRow = db.prepare(`SELECT value FROM queue_state WHERE key = 'config.ultracode_pre_prompt'`).get();
|
|
937
|
+
return {
|
|
938
|
+
pipelineTelemetryEnabled: telemetryRow?.value === 'true',
|
|
939
|
+
orchestratorModel: modelRow?.value ?? 'sonnet',
|
|
940
|
+
prePrompt: prePromptRow?.value ?? '',
|
|
941
|
+
ultraPrePrompt: ultraPrePromptRow?.value ?? '',
|
|
942
|
+
};
|
|
943
|
+
}
|
|
944
|
+
/** Resolve the effective Ultracode pre-prompt: the per-project override when
|
|
945
|
+
* set, otherwise the built-in default. */
|
|
946
|
+
function getUltracodePrePrompt(db) {
|
|
947
|
+
const override = getProjectSettings(db).ultraPrePrompt.trim();
|
|
948
|
+
return override || exports.DEFAULT_ULTRACODE_PRE_PROMPT;
|
|
949
|
+
}
|
|
950
|
+
function updateProjectSettings(db, patch) {
|
|
951
|
+
if (patch.pipelineTelemetryEnabled !== undefined) {
|
|
952
|
+
db.prepare(`INSERT OR REPLACE INTO queue_state (key, value) VALUES ('config.pipeline_telemetry_enabled', ?)`).run(patch.pipelineTelemetryEnabled ? 'true' : 'false');
|
|
953
|
+
}
|
|
954
|
+
if (patch.orchestratorModel !== undefined) {
|
|
955
|
+
db.prepare(`INSERT OR REPLACE INTO queue_state (key, value) VALUES ('config.orchestrator_model', ?)`).run(patch.orchestratorModel);
|
|
956
|
+
}
|
|
957
|
+
if (patch.prePrompt !== undefined) {
|
|
958
|
+
if (patch.prePrompt.trim() === '') {
|
|
959
|
+
db.prepare(`DELETE FROM queue_state WHERE key = 'config.pre_prompt'`).run();
|
|
960
|
+
}
|
|
961
|
+
else {
|
|
962
|
+
db.prepare(`INSERT OR REPLACE INTO queue_state (key, value) VALUES ('config.pre_prompt', ?)`).run(patch.prePrompt);
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
if (patch.ultraPrePrompt !== undefined) {
|
|
966
|
+
if (patch.ultraPrePrompt.trim() === '') {
|
|
967
|
+
db.prepare(`DELETE FROM queue_state WHERE key = 'config.ultracode_pre_prompt'`).run();
|
|
968
|
+
}
|
|
969
|
+
else {
|
|
970
|
+
db.prepare(`INSERT OR REPLACE INTO queue_state (key, value) VALUES ('config.ultracode_pre_prompt', ?)`).run(patch.ultraPrePrompt);
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
// ─── Explore Spec acceleration ────────────────────────────────────────────────
|
|
975
|
+
/**
|
|
976
|
+
* Per-project last-used value for the Quick mode Contract Refine toggle in
|
|
977
|
+
* the Add Spec modal. Default `false` when never set.
|
|
978
|
+
*/
|
|
979
|
+
function getQuickContractRefineLast(db) {
|
|
980
|
+
const row = db.prepare(`SELECT value FROM queue_state WHERE key = 'config.add_spec_quick_contract_refine_last'`).get();
|
|
981
|
+
return row?.value === 'true';
|
|
982
|
+
}
|
|
983
|
+
function hasQuickContractRefineLast(db) {
|
|
984
|
+
const row = db.prepare(`SELECT 1 FROM queue_state WHERE key = 'config.add_spec_quick_contract_refine_last'`).get();
|
|
985
|
+
return !!row;
|
|
986
|
+
}
|
|
987
|
+
function setQuickContractRefineLast(db, enabled) {
|
|
988
|
+
db.prepare(`INSERT OR REPLACE INTO queue_state (key, value) VALUES ('config.add_spec_quick_contract_refine_last', ?)`).run(enabled ? 'true' : 'false');
|
|
989
|
+
}
|
|
990
|
+
function getTelemetryBlob(db, jobId) {
|
|
991
|
+
return db.prepare('SELECT * FROM telemetry_blobs WHERE jobId = ?').get(jobId);
|
|
992
|
+
}
|
|
993
|
+
function upsertTelemetryBlob(db, row) {
|
|
994
|
+
db.prepare(`
|
|
995
|
+
INSERT INTO telemetry_blobs (jobId, path, byteSize, startedAt, endedAt, state)
|
|
996
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
997
|
+
ON CONFLICT(jobId) DO UPDATE SET
|
|
998
|
+
path = excluded.path,
|
|
999
|
+
byteSize = excluded.byteSize,
|
|
1000
|
+
startedAt = COALESCE(telemetry_blobs.startedAt, excluded.startedAt),
|
|
1001
|
+
endedAt = excluded.endedAt,
|
|
1002
|
+
state = excluded.state
|
|
1003
|
+
`).run(row.jobId, row.path ?? null, row.byteSize, row.startedAt ?? null, row.endedAt ?? null, row.state);
|
|
1004
|
+
}
|
|
1005
|
+
function listActiveTelemetryBlobs(db) {
|
|
1006
|
+
return db.prepare(`SELECT * FROM telemetry_blobs WHERE state = 'active'`).all();
|
|
1007
|
+
}
|
|
1008
|
+
function setTelemetryBlobCompacted(db, jobId) {
|
|
1009
|
+
db.prepare(`UPDATE telemetry_blobs SET state = 'compacted', path = NULL WHERE jobId = ?`).run(jobId);
|
|
1010
|
+
}
|
|
1011
|
+
function insertTelemetrySummary(db, row) {
|
|
1012
|
+
db.prepare(`
|
|
1013
|
+
INSERT OR REPLACE INTO telemetry_summaries
|
|
1014
|
+
(jobId, phase, durationMs, tokensInput, tokensOutput, tokensCache, toolCalls, apiErrors, costUsd)
|
|
1015
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1016
|
+
`).run(row.jobId, row.phase, row.durationMs ?? null, row.tokensInput ?? null, row.tokensOutput ?? null, row.tokensCache ?? null, row.toolCalls ?? null, row.apiErrors ?? null, row.costUsd ?? null);
|
|
1017
|
+
}
|
|
1018
|
+
function getTelemetrySummaries(db, jobId) {
|
|
1019
|
+
return db.prepare('SELECT * FROM telemetry_summaries WHERE jobId = ?').all(jobId);
|
|
1020
|
+
}
|
|
1021
|
+
function deleteTelemetryForJob(db, jobId) {
|
|
1022
|
+
db.prepare('DELETE FROM telemetry_blobs WHERE jobId = ?').run(jobId);
|
|
1023
|
+
db.prepare('DELETE FROM telemetry_summaries WHERE jobId = ?').run(jobId);
|
|
1024
|
+
}
|
|
1025
|
+
/** Returns a Set of jobIds that have active or compacted telemetry blobs. */
|
|
1026
|
+
function getJobsWithTelemetry(db) {
|
|
1027
|
+
const rows = db.prepare(`SELECT jobId FROM telemetry_blobs WHERE state IN ('active','compacted')`).all();
|
|
1028
|
+
return new Set(rows.map((r) => r.jobId));
|
|
1029
|
+
}
|
|
1030
|
+
/** True iff the job has an active or compacted telemetry blob row. */
|
|
1031
|
+
function hasJobTelemetry(db, jobId) {
|
|
1032
|
+
const row = db.prepare(`SELECT 1 FROM telemetry_blobs WHERE jobId = ? AND state IN ('active','compacted') LIMIT 1`).get(jobId);
|
|
1033
|
+
return row !== undefined;
|
|
1034
|
+
}
|