specrails-desktop 2.58.1 → 2.59.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -5
- package/client/dist/assets/{ActivityFeedPage-BQGppIR0.js → ActivityFeedPage-DBFt8ZHP.js} +1 -1
- package/client/dist/assets/AgentBrowserCapture-DiQx0W67.js +1 -0
- package/client/dist/assets/AgentModeAnalyticsPane-BmDSXp_J.js +2 -0
- package/client/dist/assets/AgentModeCodePane-BLQLPSwr.js +2 -0
- package/client/dist/assets/{AgentModeJobsPane-DR_06oqH.js → AgentModeJobsPane-BbTXdKUa.js} +1 -1
- package/client/dist/assets/{AgentsPage-BKIKjm_1.js → AgentsPage-Dei1jGR8.js} +3 -3
- package/client/dist/assets/{AnalyticsPage-069npRVo.js → AnalyticsPage-BhkeGIGn.js} +1 -1
- package/client/dist/assets/CodePage-B_9yYWUx.js +3 -0
- package/client/dist/assets/{DesktopAnalyticsPage-qtWAPM47.js → DesktopAnalyticsPage-DAcfEJac.js} +1 -1
- package/client/dist/assets/DocsDialog-WCuiYgsc.js +11 -0
- package/client/dist/assets/DocsPage-bGVYTMTU.js +11 -0
- package/client/dist/assets/{ExportDropdown-BcMpvV89.js → ExportDropdown-CsodZ-yb.js} +1 -1
- package/client/dist/assets/InteractiveJobComposer-D5fh_eVL.js +19 -0
- package/client/dist/assets/JobDetailModal-DlpXF61O.js +1 -0
- package/client/dist/assets/JobDetailPage-2yR19_Lb.js +1 -0
- package/client/dist/assets/JobsPage-CS9IR6Dq.js +1 -0
- package/client/dist/assets/LoopBuilderPage-BwiAtmOr.js +1 -0
- package/client/dist/assets/{LoopPreviewModal-je5pSCf-.js → LoopPreviewModal--upwvgTv.js} +1 -1
- package/client/dist/assets/LoopWindowSurface-DRnUixtl.js +1 -0
- package/client/dist/assets/LoopsPage-Xm3nRToQ.js +2 -0
- package/client/dist/assets/MinimizedChatsContext-D_8L4EWZ.js +1 -0
- package/client/dist/assets/PluginsPage-CAFQ1B4Q.js +2 -0
- package/client/dist/assets/ProjectSettingsDialog-DYSO4v90.js +1 -0
- package/client/dist/assets/{RepositoryDeliveries-BiwJuS8r.js → RepositoryDeliveries-CbQmW7Qx.js} +1 -1
- package/client/dist/assets/RepositoryScopeSelector-DKzvIGSZ.js +2 -0
- package/client/dist/assets/ReviewPacketPage-SDBhYdQB.js +4 -0
- package/client/dist/assets/TemplatePreviewModal-DHBUds51.js +1 -0
- package/client/dist/assets/TicketDetailModalContext-UAeNm7BB.js +16 -0
- package/client/dist/assets/{Trans-D3AfDylf.js → Trans-DExd3Z00.js} +1 -1
- package/client/dist/assets/agent-BElLtdKP.js +1 -0
- package/client/dist/assets/agent-C-m9VugX.js +1 -0
- package/client/dist/assets/agent-CGSKxfZk.js +1 -0
- package/client/dist/assets/agent-CW74ODDP.js +1 -0
- package/client/dist/assets/agent-DeCAZDhL.js +1 -0
- package/client/dist/assets/agent-DrM4qned.js +1 -0
- package/client/dist/assets/agent-KdIXq3w5.js +1 -0
- package/client/dist/assets/agent-jK8mCisM.js +1 -0
- package/client/dist/assets/{agentRuntime-DGz_IqKW.js → agentRuntime-15Pkk6we.js} +1 -1
- package/client/dist/assets/agentRuntime-Bpx3yWHK.js +1 -0
- package/client/dist/assets/{agentRuntime-Bg9tbmqm.js → agentRuntime-C78n13To.js} +1 -1
- package/client/dist/assets/{agentRuntime-D-GJVGkj.js → agentRuntime-CCI7XF4c.js} +1 -1
- package/client/dist/assets/{agentRuntime-D2__CVI_.js → agentRuntime-CKDHe2-a.js} +1 -1
- package/client/dist/assets/{agentRuntime-D35dGVOP.js → agentRuntime-Ddqvp2bP.js} +1 -1
- package/client/dist/assets/{agentRuntime-hInH9Fzj.js → agentRuntime-TXPQowKJ.js} +1 -1
- package/client/dist/assets/{agentRuntime-O1liDt6G.js → agentRuntime-hqZRy08E.js} +1 -1
- package/client/dist/assets/{builder-DGETbtZj.js → builder-B9gorTnT.js} +1 -1
- package/client/dist/assets/{builder-DyQperXU.js → builder-Bvw5s3Mi.js} +1 -1
- package/client/dist/assets/{builder-C1neIYGR.js → builder-CL5l4dU3.js} +1 -1
- package/client/dist/assets/{builder-DXEJUlC0.js → builder-CVDTVrQX.js} +1 -1
- package/client/dist/assets/{builder-CzW5ovyI.js → builder-CpS9liE9.js} +1 -1
- package/client/dist/assets/{builder-CvnFm0im.js → builder-DNctIL9Q.js} +1 -1
- package/client/dist/assets/{builder-D06L7iPy.js → builder-De2NxX6o.js} +1 -1
- package/client/dist/assets/{builder-C0FyvM83.js → builder-DeYT7Y-m.js} +1 -1
- package/client/dist/assets/{button-Ccc9Dc-D.js → button-D1xExSTF.js} +1 -1
- package/client/dist/assets/{common-DmsclwjP.js → common-Bdi0qN4S.js} +1 -1
- package/client/dist/assets/{common-C4Yvvhp6.js → common-Bhb9zN1P.js} +1 -1
- package/client/dist/assets/{common-od8WThlf.js → common-BykKlZg1.js} +1 -1
- package/client/dist/assets/{common-C0BOdeNi.js → common-CvnNNSJA.js} +1 -1
- package/client/dist/assets/{common-BCK7c9Pv.js → common-D5zk_mqq.js} +1 -1
- package/client/dist/assets/{common-DpLWwgI1.js → common-DQNO_al5.js} +1 -1
- package/client/dist/assets/{common-OCvl3c8t.js → common-Dax30Vmp.js} +1 -1
- package/client/dist/assets/{common-c0aujEZC.js → common-DkllpVSb.js} +1 -1
- package/client/dist/assets/{dialog-xXB2q5HJ.js → dialog-CMgps2MY.js} +1 -1
- package/client/dist/assets/external-link-D6XxC_Cu.js +1 -0
- package/client/dist/assets/file-text-BcLCfcWZ.js +1 -0
- package/client/dist/assets/{formatDistanceToNow-aBss1Nai.js → formatDistanceToNow-aw7H5ORC.js} +1 -1
- package/client/dist/assets/{getTimezoneOffsetInMilliseconds-DOGj1Mfz.js → getTimezoneOffsetInMilliseconds-NyC7gZJX.js} +1 -1
- package/client/dist/assets/index-CahchzS5.css +2 -0
- package/client/dist/assets/index-JSTZVJfe.js +83 -0
- package/client/dist/assets/integrations-BkpRUbvp.js +1 -0
- package/client/dist/assets/integrations-Bo8qbJf4.js +1 -0
- package/client/dist/assets/integrations-CmSrKzZb.js +1 -0
- package/client/dist/assets/integrations-DGtq26Rr.js +1 -0
- package/client/dist/assets/integrations-De38GFnQ.js +1 -0
- package/client/dist/assets/integrations-Dw6zdtb-.js +1 -0
- package/client/dist/assets/integrations-Dwl9KQp0.js +1 -0
- package/client/dist/assets/integrations-Refg_d2I.js +1 -0
- package/client/dist/assets/jira-api-DlXaRwA3.js +1 -0
- package/client/dist/assets/{jobs-KG4Sn12X.js → jobs-1G3wodhl.js} +1 -1
- package/client/dist/assets/{jobs-6_-r75yC.js → jobs-B4pu8TdF.js} +1 -1
- package/client/dist/assets/{jobs-pPv4KWjb.js → jobs-BD47Ov11.js} +1 -1
- package/client/dist/assets/{jobs-B1yqyId5.js → jobs-BLOoVRRl.js} +1 -1
- package/client/dist/assets/{jobs-BKiK-ZWh.js → jobs-CSrRx5lD.js} +1 -1
- package/client/dist/assets/{jobs-BFuV7ttH.js → jobs-DZAbh1m2.js} +1 -1
- package/client/dist/assets/{jobs-DmKV7b7d.js → jobs-epP7gt8W.js} +1 -1
- package/client/dist/assets/{jobs-BKa8LuwA.js → jobs-tWjcDF_6.js} +1 -1
- package/client/dist/assets/loops--KWr0X-w.js +1 -0
- package/client/dist/assets/{loops-BO3V8244.js → loops-3IutojWt.js} +1 -1
- package/client/dist/assets/{loops-C-hZK8pf.js → loops-BAosWtXE.js} +1 -1
- package/client/dist/assets/{loops-eUnCfLjC.js → loops-BISDssL5.js} +1 -1
- package/client/dist/assets/{loops-Dv849ckF.js → loops-Cm55CXJ4.js} +1 -1
- package/client/dist/assets/{loops-CDOWZ0ci.js → loops-D7tL_Saw.js} +1 -1
- package/client/dist/assets/{loops-BJDY9FTh.js → loops-DHQtnfze.js} +1 -1
- package/client/dist/assets/{loops-Q9LIgGkq.js → loops-kIJJkaxf.js} +1 -1
- package/client/dist/assets/nav--u7iPYlS.js +1 -0
- package/client/dist/assets/nav-C5Jgfaby.js +1 -0
- package/client/dist/assets/nav-CAna_Wpt.js +1 -0
- package/client/dist/assets/nav-CCLqeyHo.js +1 -0
- package/client/dist/assets/nav-Cf3c90Jk.js +1 -0
- package/client/dist/assets/nav-DMYQaQjR.js +1 -0
- package/client/dist/assets/nav-Df_PkZ9F.js +1 -0
- package/client/dist/assets/nav-VyNGBJwj.js +1 -0
- package/client/dist/assets/{project-repositories-CSnfzUpg.js → project-repositories-DXhb9FP4.js} +1 -1
- package/client/dist/assets/provider-capabilities-lePfeT_Y.js +1 -0
- package/client/dist/assets/save-OsUqVn99.js +1 -0
- package/client/dist/assets/settings-B5Hy_Kol.js +1 -0
- package/client/dist/assets/settings-BBj_kZaD.js +1 -0
- package/client/dist/assets/settings-ByVE7jdZ.js +1 -0
- package/client/dist/assets/settings-COlH_P33.js +1 -0
- package/client/dist/assets/settings-D77mC_Lb.js +1 -0
- package/client/dist/assets/settings-DZKy7MqN.js +1 -0
- package/client/dist/assets/settings-Dbyd6gmj.js +1 -0
- package/client/dist/assets/settings-Dn-pXukr.js +1 -0
- package/client/dist/assets/setup-Be485n2u.js +1 -0
- package/client/dist/assets/setup-CKGUxKr-.js +1 -0
- package/client/dist/assets/setup-Ck1Uwd7_.js +1 -0
- package/client/dist/assets/setup-Cm774a6v.js +1 -0
- package/client/dist/assets/setup-DIqMQpxt.js +1 -0
- package/client/dist/assets/setup-DfCsoNFL.js +1 -0
- package/client/dist/assets/setup-DkeE8mP0.js +1 -0
- package/client/dist/assets/setup-xVntVaz1.js +1 -0
- package/client/dist/assets/{spending-HBSk-6jv.js → spending-BdcyVkcd.js} +1 -1
- package/client/dist/assets/subscriptionUsage-Cf87DeS_.js +1 -0
- package/client/dist/assets/subscriptionUsage-D6u7VBdz.js +1 -0
- package/client/dist/assets/subscriptionUsage-DQ02J25O.js +1 -0
- package/client/dist/assets/subscriptionUsage-DWF3zKp4.js +1 -0
- package/client/dist/assets/subscriptionUsage-DkIf6Zuh.js +1 -0
- package/client/dist/assets/subscriptionUsage-DoWQv7l1.js +1 -0
- package/client/dist/assets/subscriptionUsage-N9hB-ARf.js +1 -0
- package/client/dist/assets/subscriptionUsage-zRcRPHkP.js +1 -0
- package/client/dist/assets/tauri-shell-DE6LfGPw.js +2 -0
- package/client/dist/assets/{useDesktop-0unHDBJJ.js → useDesktop-D07TvesS.js} +1 -1
- package/client/dist/assets/{useRuntimeRuns-BN1rhyEm.js → useRuntimeRuns-pGLmtAC6.js} +1 -1
- package/client/dist/assets/useSharedWebSocket-TzKtPRp9.js +2 -0
- package/client/dist/index.html +33 -32
- package/docs/README.md +1 -9
- package/docs/agent-live-steering.md +0 -6
- package/docs/agents/README.md +0 -2
- package/docs/agents/mcp.md +1 -14
- package/docs/cli.md +0 -13
- package/docs/codex.md +2 -75
- package/docs/creating-specs.md +0 -39
- package/docs/customizing.md +1 -15
- package/docs/getting-started.md +0 -23
- package/docs/guide/de/agents/2-profiles-and-the-balanced-default.md +0 -8
- package/docs/guide/de/agents/4-custom-agents-catalog.md +0 -15
- package/docs/guide/de/getting-started/1-what-is-specrails.md +0 -2
- package/docs/guide/de/getting-started/2-installing-and-first-run.md +0 -2
- package/docs/guide/de/getting-started/3-adding-your-first-project.md +0 -4
- package/docs/guide/de/getting-started/4-the-dashboard-tour.md +0 -5
- package/docs/guide/de/integrations/1-ai-providers.md +2 -50
- package/docs/guide/de/integrations/2-plugins.md +0 -8
- package/docs/guide/de/integrations/5-mcp-server.md +1 -14
- package/docs/guide/de/integrations/6-agent-chat.md +0 -11
- package/docs/guide/de/integrations/7-local-engines.md +0 -4
- package/docs/guide/de/pipeline/1-rails-and-jobs.md +0 -13
- package/docs/guide/de/pipeline/2-the-job-detail-view.md +0 -3
- package/docs/guide/de/pipeline/4-picking-an-engine-per-rail.md +0 -20
- package/docs/guide/de/pipeline/5-the-loop-builder.md +0 -6
- package/docs/guide/de/specs/2-add-spec-quick-mode.md +0 -7
- package/docs/guide/en/agents/2-profiles-and-the-balanced-default.md +0 -10
- package/docs/guide/en/agents/4-custom-agents-catalog.md +0 -25
- package/docs/guide/en/getting-started/1-what-is-specrails.md +0 -2
- package/docs/guide/en/getting-started/2-installing-and-first-run.md +0 -2
- package/docs/guide/en/getting-started/3-adding-your-first-project.md +0 -4
- package/docs/guide/en/getting-started/4-the-dashboard-tour.md +0 -5
- package/docs/guide/en/insights/3-code-explorer.md +0 -5
- package/docs/guide/en/integrations/1-ai-providers.md +10 -99
- package/docs/guide/en/integrations/2-plugins.md +0 -8
- package/docs/guide/en/integrations/5-mcp-server.md +1 -1
- package/docs/guide/en/integrations/6-agent-chat.md +0 -17
- package/docs/guide/en/integrations/7-local-engines.md +0 -4
- package/docs/guide/en/pipeline/1-rails-and-jobs.md +2 -24
- package/docs/guide/en/pipeline/2-the-job-detail-view.md +0 -3
- package/docs/guide/en/pipeline/4-picking-an-engine-per-rail.md +10 -65
- package/docs/guide/en/pipeline/5-the-loop-builder.md +0 -7
- package/docs/guide/en/specs/2-add-spec-quick-mode.md +0 -11
- package/docs/guide/es/agents/2-profiles-and-the-balanced-default.md +0 -8
- package/docs/guide/es/agents/4-custom-agents-catalog.md +0 -15
- package/docs/guide/es/getting-started/1-what-is-specrails.md +0 -2
- package/docs/guide/es/getting-started/2-installing-and-first-run.md +0 -2
- package/docs/guide/es/getting-started/3-adding-your-first-project.md +0 -4
- package/docs/guide/es/getting-started/4-the-dashboard-tour.md +0 -5
- package/docs/guide/es/integrations/1-ai-providers.md +10 -75
- package/docs/guide/es/integrations/2-plugins.md +0 -8
- package/docs/guide/es/integrations/5-mcp-server.md +1 -1
- package/docs/guide/es/integrations/6-agent-chat.md +0 -11
- package/docs/guide/es/integrations/7-local-engines.md +0 -4
- package/docs/guide/es/pipeline/1-rails-and-jobs.md +3 -13
- package/docs/guide/es/pipeline/2-the-job-detail-view.md +0 -3
- package/docs/guide/es/pipeline/4-picking-an-engine-per-rail.md +10 -53
- package/docs/guide/es/pipeline/5-the-loop-builder.md +1 -6
- package/docs/guide/es/specs/2-add-spec-quick-mode.md +0 -7
- package/docs/guide/fr/agents/2-profiles-and-the-balanced-default.md +0 -8
- package/docs/guide/fr/agents/4-custom-agents-catalog.md +0 -15
- package/docs/guide/fr/getting-started/1-what-is-specrails.md +0 -2
- package/docs/guide/fr/getting-started/2-installing-and-first-run.md +0 -2
- package/docs/guide/fr/getting-started/3-adding-your-first-project.md +0 -4
- package/docs/guide/fr/getting-started/4-the-dashboard-tour.md +0 -5
- package/docs/guide/fr/integrations/1-ai-providers.md +2 -50
- package/docs/guide/fr/integrations/2-plugins.md +0 -8
- package/docs/guide/fr/integrations/5-mcp-server.md +1 -14
- package/docs/guide/fr/integrations/6-agent-chat.md +0 -11
- package/docs/guide/fr/integrations/7-local-engines.md +0 -4
- package/docs/guide/fr/pipeline/1-rails-and-jobs.md +0 -13
- package/docs/guide/fr/pipeline/2-the-job-detail-view.md +0 -3
- package/docs/guide/fr/pipeline/4-picking-an-engine-per-rail.md +0 -22
- package/docs/guide/fr/pipeline/5-the-loop-builder.md +0 -6
- package/docs/guide/fr/specs/2-add-spec-quick-mode.md +0 -7
- package/docs/guide/it/agents/2-profiles-and-the-balanced-default.md +0 -8
- package/docs/guide/it/agents/4-custom-agents-catalog.md +0 -15
- package/docs/guide/it/getting-started/1-what-is-specrails.md +0 -2
- package/docs/guide/it/getting-started/2-installing-and-first-run.md +0 -2
- package/docs/guide/it/getting-started/3-adding-your-first-project.md +0 -4
- package/docs/guide/it/getting-started/4-the-dashboard-tour.md +0 -5
- package/docs/guide/it/integrations/1-ai-providers.md +2 -48
- package/docs/guide/it/integrations/2-plugins.md +0 -8
- package/docs/guide/it/integrations/5-mcp-server.md +1 -14
- package/docs/guide/it/integrations/6-agent-chat.md +0 -11
- package/docs/guide/it/integrations/7-local-engines.md +0 -4
- package/docs/guide/it/pipeline/1-rails-and-jobs.md +2 -13
- package/docs/guide/it/pipeline/2-the-job-detail-view.md +0 -3
- package/docs/guide/it/pipeline/4-picking-an-engine-per-rail.md +0 -20
- package/docs/guide/it/pipeline/5-the-loop-builder.md +1 -6
- package/docs/guide/it/specs/2-add-spec-quick-mode.md +0 -7
- package/docs/guide/ja/agents/2-profiles-and-the-balanced-default.md +0 -9
- package/docs/guide/ja/agents/4-custom-agents-catalog.md +0 -15
- package/docs/guide/ja/getting-started/1-what-is-specrails.md +0 -2
- package/docs/guide/ja/getting-started/2-installing-and-first-run.md +0 -2
- package/docs/guide/ja/getting-started/3-adding-your-first-project.md +0 -4
- package/docs/guide/ja/getting-started/4-the-dashboard-tour.md +0 -5
- package/docs/guide/ja/integrations/1-ai-providers.md +1 -69
- package/docs/guide/ja/integrations/2-plugins.md +0 -8
- package/docs/guide/ja/integrations/5-mcp-server.md +1 -14
- package/docs/guide/ja/integrations/6-agent-chat.md +0 -10
- package/docs/guide/ja/integrations/7-local-engines.md +0 -4
- package/docs/guide/ja/pipeline/1-rails-and-jobs.md +0 -13
- package/docs/guide/ja/pipeline/2-the-job-detail-view.md +0 -3
- package/docs/guide/ja/pipeline/4-picking-an-engine-per-rail.md +0 -21
- package/docs/guide/ja/pipeline/5-the-loop-builder.md +0 -6
- package/docs/guide/ja/specs/2-add-spec-quick-mode.md +0 -7
- package/docs/guide/pt/agents/2-profiles-and-the-balanced-default.md +0 -8
- package/docs/guide/pt/agents/4-custom-agents-catalog.md +0 -15
- package/docs/guide/pt/getting-started/1-what-is-specrails.md +0 -2
- package/docs/guide/pt/getting-started/2-installing-and-first-run.md +0 -2
- package/docs/guide/pt/getting-started/3-adding-your-first-project.md +0 -4
- package/docs/guide/pt/getting-started/4-the-dashboard-tour.md +0 -5
- package/docs/guide/pt/integrations/1-ai-providers.md +2 -48
- package/docs/guide/pt/integrations/2-plugins.md +0 -8
- package/docs/guide/pt/integrations/5-mcp-server.md +1 -14
- package/docs/guide/pt/integrations/6-agent-chat.md +0 -10
- package/docs/guide/pt/integrations/7-local-engines.md +0 -4
- package/docs/guide/pt/pipeline/1-rails-and-jobs.md +0 -13
- package/docs/guide/pt/pipeline/2-the-job-detail-view.md +0 -2
- package/docs/guide/pt/pipeline/4-picking-an-engine-per-rail.md +0 -21
- package/docs/guide/pt/pipeline/5-the-loop-builder.md +0 -6
- package/docs/guide/pt/specs/2-add-spec-quick-mode.md +0 -7
- package/docs/guide/zh/agents/2-profiles-and-the-balanced-default.md +0 -7
- package/docs/guide/zh/agents/4-custom-agents-catalog.md +0 -15
- package/docs/guide/zh/getting-started/1-what-is-specrails.md +0 -2
- package/docs/guide/zh/getting-started/2-installing-and-first-run.md +0 -2
- package/docs/guide/zh/getting-started/3-adding-your-first-project.md +0 -4
- package/docs/guide/zh/getting-started/4-the-dashboard-tour.md +0 -5
- package/docs/guide/zh/integrations/1-ai-providers.md +1 -67
- package/docs/guide/zh/integrations/2-plugins.md +0 -8
- package/docs/guide/zh/integrations/5-mcp-server.md +1 -14
- package/docs/guide/zh/integrations/6-agent-chat.md +0 -9
- package/docs/guide/zh/integrations/7-local-engines.md +0 -4
- package/docs/guide/zh/pipeline/1-rails-and-jobs.md +0 -13
- package/docs/guide/zh/pipeline/2-the-job-detail-view.md +0 -2
- package/docs/guide/zh/pipeline/4-picking-an-engine-per-rail.md +0 -20
- package/docs/guide/zh/pipeline/5-the-loop-builder.md +0 -6
- package/docs/guide/zh/specs/2-add-spec-quick-mode.md +0 -7
- package/docs/internals/adding-a-provider.md +7 -0
- package/docs/internals/desktop-owned-workflows.md +121 -0
- package/docs/internals/operations-runbook.md +20 -0
- package/docs/internals/programmatic-agent-runtime.md +34 -28
- package/docs/internals/project-builder.md +34 -11
- package/docs/internals/source-map.md +51 -3
- package/docs/internals/spec-addenda.md +11 -5
- package/docs/local-providers.md +8 -13
- package/docs/mcp.md +1 -17
- package/docs/platforms/macos.md +0 -40
- package/docs/platforms/windows-parity.md +1 -1
- package/docs/platforms/windows.md +0 -8
- package/docs/running-pipelines.md +1 -46
- package/docs/subscription-usage.md +19 -0
- package/docs/terminal.md +0 -2
- package/docs/tracking-cost.md +2 -33
- package/package.json +1 -1
- package/server/dist/core-execution.js +24 -15
- package/server/dist/desktop-db.js +23 -10
- package/server/dist/desktop-router.js +8 -4
- package/server/dist/index.js +10 -5
- package/server/dist/mcp/guide.js +1 -0
- package/server/dist/mcp/tools/jobs.js +1 -1
- package/server/dist/mcp/tools/loops.js +2 -0
- package/server/dist/mcp/tools/meta.js +6 -7
- package/server/dist/mcp/tools/projects.js +31 -6
- package/server/dist/mcp/tools/rails.js +6 -3
- package/server/dist/mcp/tools/setup.js +10 -0
- package/server/dist/mcp/tools/specs.js +1 -1
- package/server/dist/modules/accounting/runtime/pricing.js +6 -1
- package/server/dist/modules/agent-runtime/runtime/agent-runtime-bridge.js +55 -12
- package/server/dist/modules/agent-runtime/runtime/agent-runtime-metrics.js +1 -1
- package/server/dist/modules/agent-runtime/runtime/agent-runtime-settings-router.js +1 -1
- package/server/dist/modules/agent-runtime/runtime/agent-runtime-settings.js +42 -0
- package/server/dist/modules/agent-runtime/runtime/agent-runtime-verification-suggestions.js +10 -4
- package/server/dist/modules/builder/runtime/milestone-chain.js +76 -10
- package/server/dist/modules/delivery/runtime/delivery-evidence.js +3 -2
- package/server/dist/modules/delivery/runtime/multi-repo-execution.js +3 -0
- package/server/dist/modules/delivery/runtime/rail-isolated-launch.js +1 -0
- package/server/dist/modules/delivery/runtime/rail-launch-parser.js +8 -0
- package/server/dist/modules/delivery/runtime/rails-router.js +48 -26
- package/server/dist/modules/delivery/runtime/review-packet.js +1 -1
- package/server/dist/modules/loops/runtime/loop-agent-defaults.json +6 -0
- package/server/dist/modules/loops/runtime/loop-agents.js +21 -0
- package/server/dist/modules/loops/runtime/loop-command-catalog.js +1 -1
- package/server/dist/modules/loops/runtime/loop-core-factory.js +19 -3
- package/server/dist/modules/loops/runtime/loop-definition.js +79 -12
- package/server/dist/modules/loops/runtime/loop-effect.js +1 -1
- package/server/dist/modules/loops/runtime/loop-executors.js +24 -9
- package/server/dist/modules/loops/runtime/loop-factory.js +1 -1
- package/server/dist/modules/loops/runtime/loop-graph.js +5 -14
- package/server/dist/modules/loops/runtime/loop-implement-recipe.js +49 -0
- package/server/dist/modules/loops/runtime/loop-run-manager.js +3 -3
- package/server/dist/modules/loops/runtime/loop-templates.js +29 -5
- package/server/dist/modules/loops/runtime/loops-router.js +17 -0
- package/server/dist/modules/missions/runtime/agent-context-resolver.js +1 -1
- package/server/dist/modules/missions/runtime/agent-operator-prompt.js +17 -8
- package/server/dist/modules/specs/runtime/spec-models.js +4 -7
- package/server/dist/modules/subscription-usage/adapters/claude.js +113 -0
- package/server/dist/modules/subscription-usage/adapters/codex.js +164 -0
- package/server/dist/modules/subscription-usage/adapters/errors.js +15 -0
- package/server/dist/modules/subscription-usage/adapters/http.js +20 -0
- package/server/dist/modules/subscription-usage/adapters/local-auth.js +61 -0
- package/server/dist/modules/subscription-usage/application.js +11 -0
- package/server/dist/modules/subscription-usage/domain.js +70 -0
- package/server/dist/modules/subscription-usage/index.js +2 -0
- package/server/dist/modules/subscription-usage/ports.js +2 -0
- package/server/dist/modules/subscription-usage/runtime/composition.js +20 -0
- package/server/dist/modules/subscription-usage/runtime/usage-service.js +111 -0
- package/server/dist/project-registry.js +16 -1
- package/server/dist/project-repositories.js +51 -2
- package/server/dist/project-router-helpers.js +1 -1
- package/server/dist/project-router-loop-runs.js +4 -1
- package/server/dist/providers/claude-adapter.js +6 -4
- package/server/dist/providers/codex-adapter.js +4 -5
- package/server/dist/schemas/workflow-definition.schema.json +2 -0
- package/client/dist/assets/AgentBrowserCapture-PI-cxyxi.js +0 -1
- package/client/dist/assets/AgentModeAnalyticsPane-mQsOqdXx.js +0 -2
- package/client/dist/assets/AgentModeCodePane-BL-vpmlY.js +0 -2
- package/client/dist/assets/CodePage-Alitorrv.js +0 -3
- package/client/dist/assets/DocsDialog-DwL6rgcT.js +0 -11
- package/client/dist/assets/DocsPage-BA1hiJZ-.js +0 -11
- package/client/dist/assets/InteractiveJobComposer-B3H1YHr6.js +0 -19
- package/client/dist/assets/JobDetailModal-XZQ9eAAy.js +0 -1
- package/client/dist/assets/JobDetailPage-B-SIaq_q.js +0 -1
- package/client/dist/assets/JobsPage-iQXAv2mx.js +0 -1
- package/client/dist/assets/LoopBuilderPage-B_gbFAHn.js +0 -1
- package/client/dist/assets/LoopsPage-BZwcY5cu.js +0 -2
- package/client/dist/assets/MinimizedChatsContext-CNx_0bZu.js +0 -1
- package/client/dist/assets/PluginsPage-DFocnoHX.js +0 -2
- package/client/dist/assets/ProjectSettingsDialog-Bh3S6EBB.js +0 -1
- package/client/dist/assets/RepositoryScopeSelector-C2mvWBQ6.js +0 -1
- package/client/dist/assets/ReviewPacketPage-CrAf-WDy.js +0 -4
- package/client/dist/assets/TemplatePreviewModal-C7DordpN.js +0 -1
- package/client/dist/assets/TicketDetailModalContext-B-DySpKL.js +0 -16
- package/client/dist/assets/agent-BaG10AN6.js +0 -1
- package/client/dist/assets/agent-CH4aJ884.js +0 -1
- package/client/dist/assets/agent-CLc9fA7v.js +0 -1
- package/client/dist/assets/agent-DHh9-AdL.js +0 -1
- package/client/dist/assets/agent-DkKnd6Wv.js +0 -1
- package/client/dist/assets/agent-aiVhqFW1.js +0 -1
- package/client/dist/assets/agent-dbznD3eO.js +0 -1
- package/client/dist/assets/agent-sMVFcPwL.js +0 -1
- package/client/dist/assets/agentRuntime-BeUb3g9g.js +0 -1
- package/client/dist/assets/file-text-CFT-xXKN.js +0 -1
- package/client/dist/assets/index-BS7KCWkV.css +0 -2
- package/client/dist/assets/index-C4Mv6tV6.js +0 -80
- package/client/dist/assets/integrations-CH0zy1Mw.js +0 -1
- package/client/dist/assets/integrations-CIAjhd7L.js +0 -1
- package/client/dist/assets/integrations-CZdwOe8A.js +0 -1
- package/client/dist/assets/integrations-Ct7omMld.js +0 -1
- package/client/dist/assets/integrations-D6MSxy3-.js +0 -1
- package/client/dist/assets/integrations-DbZa-Zyc.js +0 -1
- package/client/dist/assets/integrations-b8pH-vfl.js +0 -1
- package/client/dist/assets/integrations-mB-7zoyT.js +0 -1
- package/client/dist/assets/jira-api-BYbEPlkr.js +0 -1
- package/client/dist/assets/layers-eDMjG41I.js +0 -1
- package/client/dist/assets/loops-B4_D-nbg.js +0 -1
- package/client/dist/assets/nav-B-tpjyc2.js +0 -1
- package/client/dist/assets/nav-BJNXGg29.js +0 -1
- package/client/dist/assets/nav-BdhtRLP7.js +0 -1
- package/client/dist/assets/nav-ByYgyN5T.js +0 -1
- package/client/dist/assets/nav-DA0Mva2c.js +0 -1
- package/client/dist/assets/nav-DcJm_Jg_.js +0 -1
- package/client/dist/assets/nav-DhMPtAXc.js +0 -1
- package/client/dist/assets/nav-DhPb-Isc.js +0 -1
- package/client/dist/assets/provider-capabilities-Vi1ZGvAK.js +0 -1
- package/client/dist/assets/save-B6wGWwBR.js +0 -1
- package/client/dist/assets/settings-3o4Ntzi5.js +0 -1
- package/client/dist/assets/settings-B97jlkwD.js +0 -1
- package/client/dist/assets/settings-BTaT_bsS.js +0 -1
- package/client/dist/assets/settings-CT8w7WiC.js +0 -1
- package/client/dist/assets/settings-Cvr8_tvh.js +0 -1
- package/client/dist/assets/settings-Dn1qgAC8.js +0 -1
- package/client/dist/assets/settings-EShDdg4J.js +0 -1
- package/client/dist/assets/settings-tfLhQZRN.js +0 -1
- package/client/dist/assets/setup-CBMu3tqH.js +0 -1
- package/client/dist/assets/setup-CPh-e6J2.js +0 -1
- package/client/dist/assets/setup-CRKLp_ke.js +0 -1
- package/client/dist/assets/setup-Cy302v4J.js +0 -1
- package/client/dist/assets/setup-D681vTPf.js +0 -1
- package/client/dist/assets/setup-DYK0VBkE.js +0 -1
- package/client/dist/assets/setup-KAuiIiVs.js +0 -1
- package/client/dist/assets/setup-OGX-oCfI.js +0 -1
- package/client/dist/assets/useSharedWebSocket-BZMrX4jr.js +0 -2
- package/client/dist/assets/wrench-BfmNO9Vw.js +0 -1
- package/docs/agy-cli-provider-study.md +0 -257
- package/docs/gemini-cli-provider-study.md +0 -301
- package/docs/gemini-core-support-evaluation.md +0 -160
- package/docs/gemini.md +0 -257
- package/docs/kimi.md +0 -291
|
@@ -0,0 +1,164 @@
|
|
|
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.createCodexReader = createCodexReader;
|
|
7
|
+
const node_string_decoder_1 = require("node:string_decoder");
|
|
8
|
+
const node_os_1 = require("node:os");
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
const win_spawn_1 = require("../../../util/win-spawn");
|
|
11
|
+
const domain_1 = require("../domain");
|
|
12
|
+
const errors_1 = require("./errors");
|
|
13
|
+
const local_auth_1 = require("./local-auth");
|
|
14
|
+
async function stopProbe(child) {
|
|
15
|
+
if (child.exitCode !== null || child.signalCode !== null)
|
|
16
|
+
return;
|
|
17
|
+
await new Promise(resolve => {
|
|
18
|
+
const done = () => { clearTimeout(timer); child.off('close', done); resolve(); };
|
|
19
|
+
const timer = setTimeout(() => {
|
|
20
|
+
if (child.pid)
|
|
21
|
+
(0, win_spawn_1.treeKillSafe)(child.pid, 'SIGKILL', () => done());
|
|
22
|
+
else {
|
|
23
|
+
child.kill('SIGKILL');
|
|
24
|
+
done();
|
|
25
|
+
}
|
|
26
|
+
}, 500);
|
|
27
|
+
child.once('close', done);
|
|
28
|
+
child.stdin?.end();
|
|
29
|
+
if (child.pid)
|
|
30
|
+
(0, win_spawn_1.treeKillSafe)(child.pid, 'SIGTERM');
|
|
31
|
+
else
|
|
32
|
+
child.kill('SIGTERM');
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
function createCodexReader(options = {}) {
|
|
36
|
+
const home = options.home ?? process.env.CODEX_HOME ?? node_path_1.default.join((0, node_os_1.homedir)(), '.codex');
|
|
37
|
+
return {
|
|
38
|
+
async context() {
|
|
39
|
+
if (!node_path_1.default.isAbsolute(home))
|
|
40
|
+
throw new errors_1.UsageError('unsupported-platform');
|
|
41
|
+
return (0, local_auth_1.fingerprint)(`${home}:${await (options.readFile ?? local_auth_1.readAuthFile)(node_path_1.default.join(home, 'auth.json')) ?? 'keychain-or-none'}`);
|
|
42
|
+
},
|
|
43
|
+
read(signal) {
|
|
44
|
+
if (signal.aborted)
|
|
45
|
+
return Promise.reject(new errors_1.UsageError('aborted'));
|
|
46
|
+
return new Promise((resolve, reject) => {
|
|
47
|
+
let child;
|
|
48
|
+
try {
|
|
49
|
+
child = options.spawn ? options.spawn() : (0, win_spawn_1.spawnCli)('codex', ['app-server', '--listen', 'stdio://'], {
|
|
50
|
+
cwd: (0, node_os_1.homedir)(), env: { ...process.env, CODEX_HOME: home }, stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
reject(new errors_1.UsageError('unsupported-cli'));
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
let buffer = '', finished = false, plan = null, identity;
|
|
58
|
+
const decoder = new node_string_decoder_1.StringDecoder('utf8');
|
|
59
|
+
const timer = setTimeout(() => finish(new errors_1.UsageError('probe-timeout', true)), options.timeoutMs ?? 25_000);
|
|
60
|
+
function cleanup() {
|
|
61
|
+
clearTimeout(timer);
|
|
62
|
+
signal.removeEventListener('abort', abort);
|
|
63
|
+
child.stdout?.off('data', data);
|
|
64
|
+
child.stderr?.off('data', drain);
|
|
65
|
+
child.off('error', failure);
|
|
66
|
+
child.off('close', closed);
|
|
67
|
+
child.stdin?.off('error', failure);
|
|
68
|
+
}
|
|
69
|
+
function finish(error, result) {
|
|
70
|
+
if (finished)
|
|
71
|
+
return;
|
|
72
|
+
finished = true;
|
|
73
|
+
cleanup();
|
|
74
|
+
// Continue draining without retaining provider diagnostics during termination.
|
|
75
|
+
child.stdout?.resume();
|
|
76
|
+
child.stderr?.resume();
|
|
77
|
+
void (options.stop ?? stopProbe)(child).then(() => error ? reject(error) : resolve(result), () => reject(new errors_1.UsageError('cleanup-failed', true)));
|
|
78
|
+
}
|
|
79
|
+
const abort = () => finish(new errors_1.UsageError('aborted'));
|
|
80
|
+
const failure = () => finish(new errors_1.UsageError('unsupported-cli'));
|
|
81
|
+
const closed = () => finish(new errors_1.UsageError('probe-exited', true));
|
|
82
|
+
const drain = () => { };
|
|
83
|
+
function send(id, method, params = {}) {
|
|
84
|
+
try {
|
|
85
|
+
child.stdin?.write(`${JSON.stringify({ id, method, params })}\n`);
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
failure();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function data(chunk) {
|
|
92
|
+
buffer += decoder.write(chunk);
|
|
93
|
+
if (Buffer.byteLength(buffer) > 1024 * 1024) {
|
|
94
|
+
finish(new errors_1.UsageError('invalid-response', true));
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
let newline;
|
|
98
|
+
while (!finished && (newline = buffer.indexOf('\n')) >= 0) {
|
|
99
|
+
const line = buffer.slice(0, newline);
|
|
100
|
+
buffer = buffer.slice(newline + 1);
|
|
101
|
+
let message;
|
|
102
|
+
try {
|
|
103
|
+
message = (0, domain_1.record)(JSON.parse(line));
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
finish(new errors_1.UsageError('invalid-response', true));
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (typeof message.id !== 'number')
|
|
110
|
+
continue;
|
|
111
|
+
if (message.error) {
|
|
112
|
+
const code = (0, domain_1.record)(message.error).code;
|
|
113
|
+
finish(new errors_1.UsageError(code === -32601 ? 'unsupported-cli' : 'provider-error', code !== -32601));
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (message.id === 1) {
|
|
117
|
+
try {
|
|
118
|
+
child.stdin?.write(`${JSON.stringify({ method: 'initialized', params: {} })}\n`);
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
failure();
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
send(2, 'account/read', { refreshToken: false });
|
|
125
|
+
}
|
|
126
|
+
else if (message.id === 2) {
|
|
127
|
+
const account = (0, domain_1.record)((0, domain_1.record)(message.result).account);
|
|
128
|
+
if (!Object.keys(account).length) {
|
|
129
|
+
finish(undefined, { availability: 'signed-out', windows: [], plan: null, source: null });
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (account.type !== 'chatgpt') {
|
|
133
|
+
finish(undefined, { availability: 'unsupported-auth', windows: [], plan: null, source: null });
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
plan = typeof account.planType === 'string' ? account.planType.slice(0, 80) : null;
|
|
137
|
+
// Never export an email; only an internal fingerprint for identity checks.
|
|
138
|
+
if (typeof account.email === 'string')
|
|
139
|
+
identity = (0, local_auth_1.fingerprint)(account.email);
|
|
140
|
+
send(3, 'account/rateLimits/read');
|
|
141
|
+
}
|
|
142
|
+
else if (message.id === 3) {
|
|
143
|
+
const payload = (0, domain_1.record)(message.result), windows = (0, domain_1.normalizeCodex)(payload);
|
|
144
|
+
if (typeof payload.accountId === 'string')
|
|
145
|
+
identity = (0, local_auth_1.fingerprint)(payload.accountId);
|
|
146
|
+
if (!windows.length) {
|
|
147
|
+
finish(new errors_1.UsageError('usage-unavailable'));
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
finish(undefined, { availability: 'available', windows, plan, source: 'app-server', identity });
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
155
|
+
child.on('error', failure);
|
|
156
|
+
child.on('close', closed);
|
|
157
|
+
child.stdin?.on('error', failure);
|
|
158
|
+
child.stdout?.on('data', data);
|
|
159
|
+
child.stderr?.on('data', drain);
|
|
160
|
+
send(1, 'initialize', { clientInfo: { name: 'specrails-subscription-usage', title: 'Specrails', version: '1.0.0' } });
|
|
161
|
+
});
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.UsageError = void 0;
|
|
4
|
+
class UsageError extends Error {
|
|
5
|
+
code;
|
|
6
|
+
retryable;
|
|
7
|
+
retryMs;
|
|
8
|
+
constructor(code, retryable = false, retryMs) {
|
|
9
|
+
super(code);
|
|
10
|
+
this.code = code;
|
|
11
|
+
this.retryable = retryable;
|
|
12
|
+
this.retryMs = retryMs;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
exports.UsageError = UsageError;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerUsageRoutes = registerUsageRoutes;
|
|
4
|
+
const domain_1 = require("../domain");
|
|
5
|
+
function registerUsageRoutes(router, service) {
|
|
6
|
+
router.get('/subscription-usage', (_req, res) => res.json(service.snapshot()));
|
|
7
|
+
router.post('/subscription-usage/refresh', (req, res) => {
|
|
8
|
+
const body = req.body ?? {};
|
|
9
|
+
if (typeof body !== 'object' || body === null || Array.isArray(body) || Object.keys(body).some(key => key !== 'providerId' && key !== 'automatic')) {
|
|
10
|
+
res.status(400).json({ error: 'Invalid usage refresh request' });
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
const { providerId, automatic } = body;
|
|
14
|
+
if ((providerId !== undefined && !domain_1.USAGE_PROVIDERS.includes(providerId)) || (automatic !== undefined && typeof automatic !== 'boolean')) {
|
|
15
|
+
res.status(400).json({ error: 'Invalid usage refresh request' });
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
res.status(202).json(service.refresh(providerId, automatic === true));
|
|
19
|
+
});
|
|
20
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.fingerprint = void 0;
|
|
4
|
+
exports.readAuthFile = readAuthFile;
|
|
5
|
+
exports.limitedJson = limitedJson;
|
|
6
|
+
const promises_1 = require("node:fs/promises");
|
|
7
|
+
const node_crypto_1 = require("node:crypto");
|
|
8
|
+
const errors_1 = require("./errors");
|
|
9
|
+
const fingerprint = (value) => (0, node_crypto_1.createHash)('sha256').update(value).digest('hex');
|
|
10
|
+
exports.fingerprint = fingerprint;
|
|
11
|
+
/** Bounded read; never expose auth bytes or paths in errors. */
|
|
12
|
+
async function readAuthFile(file) {
|
|
13
|
+
let handle;
|
|
14
|
+
try {
|
|
15
|
+
handle = await (0, promises_1.open)(file, 'r');
|
|
16
|
+
const stat = await handle.stat();
|
|
17
|
+
if (!stat.isFile() || stat.size > 1024 * 1024)
|
|
18
|
+
throw new errors_1.UsageError('credentials-unreadable');
|
|
19
|
+
const buffer = Buffer.alloc(1024 * 1024 + 1);
|
|
20
|
+
const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0);
|
|
21
|
+
if (bytesRead > 1024 * 1024)
|
|
22
|
+
throw new errors_1.UsageError('credentials-unreadable');
|
|
23
|
+
return buffer.toString('utf8', 0, bytesRead);
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
if (error.code === 'ENOENT')
|
|
27
|
+
return null;
|
|
28
|
+
throw new errors_1.UsageError('credentials-unreadable');
|
|
29
|
+
}
|
|
30
|
+
finally {
|
|
31
|
+
await handle?.close();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
async function limitedJson(response) {
|
|
35
|
+
const reader = response.body?.getReader();
|
|
36
|
+
if (!reader)
|
|
37
|
+
throw new errors_1.UsageError('invalid-response', true);
|
|
38
|
+
let size = 0;
|
|
39
|
+
const chunks = [];
|
|
40
|
+
try {
|
|
41
|
+
while (true) {
|
|
42
|
+
const { done, value } = await reader.read();
|
|
43
|
+
if (done)
|
|
44
|
+
break;
|
|
45
|
+
size += value.length;
|
|
46
|
+
if (size > 1024 * 1024)
|
|
47
|
+
throw new errors_1.UsageError('invalid-response', true);
|
|
48
|
+
chunks.push(value);
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
return JSON.parse(Buffer.concat(chunks).toString('utf8'));
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
throw new errors_1.UsageError('invalid-response', true);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
await reader.cancel().catch(() => { });
|
|
59
|
+
reader.releaseLock();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.withFreshness = withFreshness;
|
|
4
|
+
exports.backoff = backoff;
|
|
5
|
+
function withFreshness(row, now) {
|
|
6
|
+
if (!row.observedAt)
|
|
7
|
+
return { ...row, freshness: 'unknown' };
|
|
8
|
+
const expired = now - Date.parse(row.observedAt) >= 300_000 || row.windows.some(w => w.resetsAt && Date.parse(w.resetsAt) <= now);
|
|
9
|
+
return { ...row, freshness: expired || row.refreshState === 'error' ? 'stale' : 'fresh' };
|
|
10
|
+
}
|
|
11
|
+
function backoff(failures) { return Math.min(300_000, 30_000 * 2 ** Math.min(4, Math.max(0, failures - 1))); }
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.duration = exports.percentage = exports.record = exports.USAGE_PROVIDERS = void 0;
|
|
4
|
+
exports.timestamp = timestamp;
|
|
5
|
+
exports.windowLabel = windowLabel;
|
|
6
|
+
exports.normalizeClaude = normalizeClaude;
|
|
7
|
+
exports.normalizeCodex = normalizeCodex;
|
|
8
|
+
exports.initialUsage = initialUsage;
|
|
9
|
+
exports.USAGE_PROVIDERS = ['claude', 'codex'];
|
|
10
|
+
const record = (value) => value !== null && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
11
|
+
exports.record = record;
|
|
12
|
+
const percentage = (value) => typeof value === 'number' && Number.isFinite(value) && value >= 0 && value <= 100 ? value : null;
|
|
13
|
+
exports.percentage = percentage;
|
|
14
|
+
const duration = (value) => typeof value === 'number' && Number.isFinite(value) && value > 0 ? value : null;
|
|
15
|
+
exports.duration = duration;
|
|
16
|
+
function timestamp(value, seconds = false) {
|
|
17
|
+
const time = typeof value === 'number' && Number.isFinite(value) ? value * (seconds ? 1000 : 1) : typeof value === 'string' ? Date.parse(value) : NaN;
|
|
18
|
+
return Number.isFinite(time) && time > 0 && time <= 8.64e15 ? new Date(time).toISOString() : null;
|
|
19
|
+
}
|
|
20
|
+
function windowLabel(minutes) {
|
|
21
|
+
return minutes === 300 ? 'session' : minutes === 10080 ? 'weekly' : minutes === 43200 ? 'monthly' : 'window';
|
|
22
|
+
}
|
|
23
|
+
function normalizeClaude(payload) {
|
|
24
|
+
const data = (0, exports.record)(payload);
|
|
25
|
+
const windows = [];
|
|
26
|
+
for (const [key, raw] of Object.entries(data)) {
|
|
27
|
+
if (!/^(five_hour|seven_day(?:_[a-z0-9_]+)?|fable_(weekly|seven_day))$/.test(key) || raw === null)
|
|
28
|
+
continue;
|
|
29
|
+
const value = (0, exports.record)(raw);
|
|
30
|
+
if (!['utilization', 'used_percentage', 'resets_at'].some(field => Object.hasOwn(value, field)))
|
|
31
|
+
continue;
|
|
32
|
+
const minutes = key === 'five_hour' ? 300 : 10080;
|
|
33
|
+
const model = key.startsWith('seven_day_') ? key.slice(10) : key.startsWith('fable_') ? 'fable' : null;
|
|
34
|
+
windows.push({ id: key, label: windowLabel(minutes), scope: model ? 'model' : 'account', model,
|
|
35
|
+
usedPercent: (0, exports.percentage)(value.utilization ?? value.used_percentage), durationMinutes: minutes, resetsAt: timestamp(value.resets_at, typeof value.resets_at === 'number') });
|
|
36
|
+
}
|
|
37
|
+
if (Array.isArray(data.limits))
|
|
38
|
+
for (const raw of data.limits.slice(0, 32)) {
|
|
39
|
+
const value = (0, exports.record)(raw), model = (0, exports.record)((0, exports.record)(value.scope).model).display_name;
|
|
40
|
+
if (value.kind !== 'weekly_scoped' || typeof model !== 'string' || !model.trim())
|
|
41
|
+
continue;
|
|
42
|
+
const name = model.trim().slice(0, 80);
|
|
43
|
+
if (windows.some(w => w.model?.toLowerCase() === name.toLowerCase()))
|
|
44
|
+
continue;
|
|
45
|
+
windows.push({ id: `model:${name}`, label: 'weekly', scope: 'model', model: name,
|
|
46
|
+
usedPercent: (0, exports.percentage)(value.percent), durationMinutes: 10080, resetsAt: timestamp(value.resets_at, true) });
|
|
47
|
+
}
|
|
48
|
+
return windows;
|
|
49
|
+
}
|
|
50
|
+
function normalizeCodex(payload) {
|
|
51
|
+
const data = (0, exports.record)(payload), groups = (0, exports.record)(data.rateLimitsByLimitId);
|
|
52
|
+
const entries = Object.keys(groups).length ? Object.entries(groups) : [['codex', data.rateLimits]];
|
|
53
|
+
const windows = [];
|
|
54
|
+
for (const [group, raw] of entries.slice(0, 32)) {
|
|
55
|
+
const bucket = (0, exports.record)(raw);
|
|
56
|
+
for (const slot of ['primary', 'secondary']) {
|
|
57
|
+
if (!bucket[slot] || typeof bucket[slot] !== 'object')
|
|
58
|
+
continue;
|
|
59
|
+
const value = (0, exports.record)(bucket[slot]), minutes = (0, exports.duration)(value.windowDurationMins);
|
|
60
|
+
windows.push({ id: `${group}:${slot}`, label: windowLabel(minutes), scope: group === 'codex' ? 'account' : 'model',
|
|
61
|
+
model: group === 'codex' ? null : (typeof bucket.limitName === 'string' ? bucket.limitName : group).slice(0, 80),
|
|
62
|
+
usedPercent: (0, exports.percentage)(value.usedPercent), durationMinutes: minutes, resetsAt: timestamp(value.resetsAt, true) });
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return windows;
|
|
66
|
+
}
|
|
67
|
+
function initialUsage(providerId, generation) {
|
|
68
|
+
return { providerId, installed: null, generation, availability: 'unavailable', refreshState: 'idle', freshness: 'unknown', plan: null,
|
|
69
|
+
windows: [], source: null, observedAt: null, attemptedAt: null, retryAt: null, issue: null };
|
|
70
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createDesktopUsageService = createDesktopUsageService;
|
|
4
|
+
const provider_detection_1 = require("../../../provider-detection");
|
|
5
|
+
const claude_1 = require("../adapters/claude");
|
|
6
|
+
const codex_1 = require("../adapters/codex");
|
|
7
|
+
const usage_service_1 = require("./usage-service");
|
|
8
|
+
function createDesktopUsageService() {
|
|
9
|
+
return (0, usage_service_1.createUsageService)({
|
|
10
|
+
installed: async (id) => {
|
|
11
|
+
const provider = (await (0, provider_detection_1.getDetectionSnapshot)()).providers[id];
|
|
12
|
+
return !!provider?.installed;
|
|
13
|
+
},
|
|
14
|
+
eligible: async (id) => {
|
|
15
|
+
const provider = (await (0, provider_detection_1.getDetectionSnapshot)()).providers[id];
|
|
16
|
+
return !!provider?.executable && provider.meetsMinimum !== false && !provider.vetoed;
|
|
17
|
+
},
|
|
18
|
+
readers: { claude: (0, claude_1.createClaudeReader)(), codex: (0, codex_1.createCodexReader)() },
|
|
19
|
+
});
|
|
20
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createUsageService = createUsageService;
|
|
4
|
+
const node_crypto_1 = require("node:crypto");
|
|
5
|
+
const domain_1 = require("../domain");
|
|
6
|
+
const application_1 = require("../application");
|
|
7
|
+
const errors_1 = require("../adapters/errors");
|
|
8
|
+
function createUsageService(deps) {
|
|
9
|
+
const now = deps.now ?? Date.now;
|
|
10
|
+
const instanceId = (0, node_crypto_1.randomUUID)();
|
|
11
|
+
const rows = new Map(domain_1.USAGE_PROVIDERS.map(id => [id, (0, domain_1.initialUsage)(id, (0, node_crypto_1.randomUUID)())]));
|
|
12
|
+
const contexts = new Map(), identities = new Map();
|
|
13
|
+
const failures = new Map(), running = new Map();
|
|
14
|
+
const controllers = new Map();
|
|
15
|
+
let revision = 0, disposed = false;
|
|
16
|
+
function snapshot() { return { scope: 'machine', instanceId, revision, providers: domain_1.USAGE_PROVIDERS.map(id => (0, application_1.withFreshness)(rows.get(id), now())) }; }
|
|
17
|
+
function clear(id) {
|
|
18
|
+
const previous = rows.get(id);
|
|
19
|
+
rows.set(id, { ...(0, domain_1.initialUsage)(id, (0, node_crypto_1.randomUUID)()), installed: previous.installed, attemptedAt: previous.attemptedAt, refreshState: previous.refreshState });
|
|
20
|
+
identities.delete(id);
|
|
21
|
+
revision++;
|
|
22
|
+
}
|
|
23
|
+
async function collect(id, signal) {
|
|
24
|
+
const reader = deps.readers[id];
|
|
25
|
+
try {
|
|
26
|
+
const installed = await deps.installed(id);
|
|
27
|
+
if (signal.aborted)
|
|
28
|
+
return;
|
|
29
|
+
rows.set(id, { ...rows.get(id), installed });
|
|
30
|
+
if (!installed) {
|
|
31
|
+
clear(id);
|
|
32
|
+
rows.set(id, { ...rows.get(id), refreshState: 'idle', retryAt: new Date(now() + 30_000).toISOString(), issue: { code: 'cli-missing', retryable: false } });
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (deps.eligible && !await deps.eligible(id))
|
|
36
|
+
throw new errors_1.UsageError('unsupported-cli');
|
|
37
|
+
if (signal.aborted)
|
|
38
|
+
return;
|
|
39
|
+
const before = await reader.context(signal);
|
|
40
|
+
if (signal.aborted)
|
|
41
|
+
return;
|
|
42
|
+
if (contexts.get(id) !== before) {
|
|
43
|
+
clear(id);
|
|
44
|
+
contexts.set(id, before);
|
|
45
|
+
}
|
|
46
|
+
const result = await reader.read(signal);
|
|
47
|
+
const after = await reader.context(signal);
|
|
48
|
+
if (signal.aborted)
|
|
49
|
+
return;
|
|
50
|
+
if (before !== after) {
|
|
51
|
+
clear(id);
|
|
52
|
+
contexts.set(id, after);
|
|
53
|
+
throw new errors_1.UsageError('account-changed', true);
|
|
54
|
+
}
|
|
55
|
+
if (result.identity && identities.get(id) && identities.get(id) !== result.identity)
|
|
56
|
+
clear(id);
|
|
57
|
+
if (result.identity)
|
|
58
|
+
identities.set(id, result.identity);
|
|
59
|
+
const available = result.availability === 'available';
|
|
60
|
+
if (!available)
|
|
61
|
+
clear(id);
|
|
62
|
+
rows.set(id, { ...rows.get(id), availability: result.availability, plan: result.plan, source: result.source, windows: available ? result.windows : [],
|
|
63
|
+
// Identity is internal: explicitly select fields instead of returning adapter metadata.
|
|
64
|
+
providerId: id, installed: true, refreshState: 'idle', freshness: available ? 'fresh' : 'unknown',
|
|
65
|
+
observedAt: available ? new Date(now()).toISOString() : null, retryAt: new Date(now() + 30_000).toISOString(),
|
|
66
|
+
issue: available ? null : { code: result.availability, retryable: false } });
|
|
67
|
+
failures.delete(id);
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
if (signal.aborted)
|
|
71
|
+
return;
|
|
72
|
+
const issue = error instanceof errors_1.UsageError ? error : new errors_1.UsageError('collection-failed', true);
|
|
73
|
+
const count = (failures.get(id) ?? 0) + 1;
|
|
74
|
+
failures.set(id, count);
|
|
75
|
+
if (!issue.retryable)
|
|
76
|
+
clear(id);
|
|
77
|
+
const row = rows.get(id);
|
|
78
|
+
rows.set(id, { ...row, refreshState: 'error', availability: row.observedAt ? 'available' : (['signed-out', 'unsupported-cli', 'unsupported-platform', 'unsupported-auth'].includes(issue.code) ? issue.code : 'unavailable'),
|
|
79
|
+
issue: { code: issue.code, retryable: issue.retryable }, retryAt: new Date(now() + (issue.retryMs ?? (0, application_1.backoff)(count))).toISOString() });
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
revision++;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function refresh(providerId, automatic = false) {
|
|
86
|
+
let scheduled = false;
|
|
87
|
+
if (!disposed)
|
|
88
|
+
for (const id of providerId ? [providerId] : domain_1.USAGE_PROVIDERS) {
|
|
89
|
+
const row = rows.get(id);
|
|
90
|
+
if (running.has(id)) {
|
|
91
|
+
scheduled = true;
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (row.retryAt && Date.parse(row.retryAt) > now())
|
|
95
|
+
continue;
|
|
96
|
+
if (automatic && row.issue && !row.issue.retryable)
|
|
97
|
+
continue;
|
|
98
|
+
const controller = new AbortController();
|
|
99
|
+
controllers.set(id, controller);
|
|
100
|
+
rows.set(id, { ...row, attemptedAt: new Date(now()).toISOString(), refreshState: 'refreshing' });
|
|
101
|
+
revision++;
|
|
102
|
+
scheduled = true;
|
|
103
|
+
const task = collect(id, controller.signal).finally(() => { running.delete(id); controllers.delete(id); });
|
|
104
|
+
running.set(id, task);
|
|
105
|
+
}
|
|
106
|
+
const view = snapshot();
|
|
107
|
+
return { snapshot: view, scheduled, retryAt: view.providers.filter(p => !providerId || p.providerId === providerId).map(p => p.retryAt).filter((s) => !!s).sort()[0] ?? null };
|
|
108
|
+
}
|
|
109
|
+
async function dispose() { disposed = true; controllers.forEach(c => c.abort()); await Promise.allSettled(running.values()); }
|
|
110
|
+
return { snapshot, refresh, dispose, settled: () => Promise.allSettled(running.values()) };
|
|
111
|
+
}
|
|
@@ -494,6 +494,15 @@ class ProjectRegistry {
|
|
|
494
494
|
delete input.path;
|
|
495
495
|
}
|
|
496
496
|
}
|
|
497
|
+
if (input.workspacePath !== undefined || input.workspacePaths !== undefined) {
|
|
498
|
+
const context = this._contexts.get(projectId);
|
|
499
|
+
if (!context)
|
|
500
|
+
throw new project_repositories_1.RepositoryValidationError('Load this project before changing its workspace', 'project_unavailable', 503);
|
|
501
|
+
const references = (0, multi_repo_execution_store_1.getRepositoryExecutionReferences)(context.db, repositoryId);
|
|
502
|
+
const unfinished = context.db.prepare("SELECT id FROM loop_runs WHERE status <> 'completed' LIMIT 1").get();
|
|
503
|
+
if (unfinished || references.runIds.length || references.deliveryIds.length)
|
|
504
|
+
throw new project_repositories_1.RepositoryValidationError('Workspace is referenced by runs or pending deliveries', 'repository_in_use', 409, references);
|
|
505
|
+
}
|
|
497
506
|
if (input.path !== undefined)
|
|
498
507
|
this._assertRepositoryUnreferenced(projectId, repositoryId);
|
|
499
508
|
const repository = (0, desktop_db_1.updateProjectRepository)(this._desktopDb, projectId, repositoryId, input);
|
|
@@ -1108,7 +1117,8 @@ class ProjectRegistry {
|
|
|
1108
1117
|
const loopRunManager = new loop_run_manager_1.LoopRunManager(db, boundBroadcast, (0, loop_executors_1.createLoopExecutors)({
|
|
1109
1118
|
pluginScope: () => ({ stateRoot: (0, workspace_resolution_1.resolveProjectExecution)(project).cwd, legacyProviderId: project.provider }),
|
|
1110
1119
|
// A project registered as a package of a larger checkout scopes Core to that package in isolated worktrees.
|
|
1111
|
-
sourcePath: () => project.path,
|
|
1120
|
+
sourcePath: () => (0, project_repositories_1.resolveProjectRepository)(project).path,
|
|
1121
|
+
workspacePaths: () => { const primary = (0, project_repositories_1.resolveProjectRepository)(project); return primary.workspacePaths ?? (primary.workspacePath ? [primary.workspacePath] : undefined); },
|
|
1112
1122
|
env: () => (0, workspace_resolution_1.resolveLoopBaseEnv)({ slug: project.slug, path: project.path }, undefined, (0, project_env_1.applyWorktreeEnvPassthrough)(db, process.env)),
|
|
1113
1123
|
// Named rail profiles and global per-agent defaults ride the same
|
|
1114
1124
|
// SPECRAILS_PROFILE_PATH snapshot in both AI transports. An explicit null
|
|
@@ -1363,6 +1373,11 @@ class ProjectRegistry {
|
|
|
1363
1373
|
: typeof data.jobId === 'string' ? [data.jobId] : [];
|
|
1364
1374
|
return { ok: true, loopRunIds: ids.filter((v) => typeof v === 'string') };
|
|
1365
1375
|
},
|
|
1376
|
+
integrateDelivery: async (delivery) => {
|
|
1377
|
+
const action = delivery.decision === 'no_changes' ? 'acknowledge-no-changes' : 'merge-local';
|
|
1378
|
+
const r = await internalApi.call('POST', `${railsBase}/pr-decision`, { prDeliveryId: delivery.id, expectedDecision: delivery.decision, action });
|
|
1379
|
+
return r.ok ? { ok: true } : { ok: false, status: r.status, ...(0, internal_api_1.internalApiError)(r) };
|
|
1380
|
+
},
|
|
1366
1381
|
activeDeliveryForRail: (railIndex) => {
|
|
1367
1382
|
const row = (0, rail_pr_store_1.getActivePrDeliveryByRail)(db, railIndex);
|
|
1368
1383
|
return row ? (0, rail_pr_store_1.toPrDeliverySnapshot)(row) : null;
|
|
@@ -15,6 +15,7 @@ exports.inspectRepositoryPath = inspectRepositoryPath;
|
|
|
15
15
|
exports.assertDistinctRepositories = assertDistinctRepositories;
|
|
16
16
|
exports.isRepositoryPathWithin = isRepositoryPathWithin;
|
|
17
17
|
exports.resolveRepositoryProject = resolveRepositoryProject;
|
|
18
|
+
exports.validateWorkspaceSelection = validateWorkspaceSelection;
|
|
18
19
|
const fs_1 = __importDefault(require("fs"));
|
|
19
20
|
const path_1 = __importDefault(require("path"));
|
|
20
21
|
const child_process_1 = require("child_process");
|
|
@@ -117,6 +118,33 @@ function inspectRepositoryPath(input, requireAvailable = true) {
|
|
|
117
118
|
const canonical = canonicalRepositoryPath(input.path);
|
|
118
119
|
if (requireAvailable && !repositoryAvailable(canonical))
|
|
119
120
|
throw new RepositoryValidationError(`Repository directory is unavailable: ${canonical}`, 'repository_unavailable');
|
|
121
|
+
if (input.workspacePath != null && input.workspacePaths != null)
|
|
122
|
+
throw new RepositoryValidationError('Use workspacePaths or workspacePath, not both', 'invalid_workspace_path');
|
|
123
|
+
let workspacePaths;
|
|
124
|
+
if (input.workspacePaths !== undefined && input.workspacePaths !== null) {
|
|
125
|
+
if (!Array.isArray(input.workspacePaths) || !input.workspacePaths.length || input.workspacePaths.some(item => typeof item !== 'string' || !item.trim()))
|
|
126
|
+
throw new RepositoryValidationError('workspacePaths must be a nonempty array of workspace directories');
|
|
127
|
+
workspacePaths = input.workspacePaths.map(item => inspectRepositoryPath({ path: canonical, workspacePath: item }, requireAvailable).workspacePath);
|
|
128
|
+
if (new Set(workspacePaths.map(repositoryPathKey)).size !== workspacePaths.length || workspacePaths.some((root, index) => workspacePaths.some((other, otherIndex) => index !== otherIndex && isRepositoryPathWithin(root, other))))
|
|
129
|
+
throw new RepositoryValidationError('Code workspaces must have distinct non-overlapping directories', 'invalid_workspace_path');
|
|
130
|
+
}
|
|
131
|
+
let workspacePath;
|
|
132
|
+
if (input.workspacePath !== undefined && input.workspacePath !== null) {
|
|
133
|
+
if (typeof input.workspacePath !== 'string' || !input.workspacePath.trim())
|
|
134
|
+
throw new RepositoryValidationError('Workspace path must not be empty');
|
|
135
|
+
workspacePath = canonicalRepositoryPath(path_1.default.resolve(canonical, input.workspacePath));
|
|
136
|
+
if (!isRepositoryPathWithin(canonical, workspacePath))
|
|
137
|
+
throw new RepositoryValidationError('Workspace must be inside its registered repository', 'invalid_workspace_path');
|
|
138
|
+
if (requireAvailable && !repositoryAvailable(workspacePath))
|
|
139
|
+
throw new RepositoryValidationError(`Workspace directory is unavailable: ${workspacePath}`, 'workspace_unavailable');
|
|
140
|
+
// A nested independent Git checkout needs its own membership and delivery.
|
|
141
|
+
if (workspacePath !== canonical) {
|
|
142
|
+
for (let current = workspacePath; current !== canonical; current = path_1.default.dirname(current)) {
|
|
143
|
+
if (fs_1.default.existsSync(path_1.default.join(current, '.git')))
|
|
144
|
+
throw new RepositoryValidationError('A nested Git repository must be registered as a separate workspace', 'invalid_workspace_path');
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
120
148
|
let gitIdentity = null;
|
|
121
149
|
if (repositoryAvailable(canonical)) {
|
|
122
150
|
try {
|
|
@@ -125,14 +153,15 @@ function inspectRepositoryPath(input, requireAvailable = true) {
|
|
|
125
153
|
}
|
|
126
154
|
catch { /* Non-Git context folders are supported. */ }
|
|
127
155
|
}
|
|
128
|
-
return { ...input, path: canonical, name: input.name?.trim(), integrationBranch: input.integrationBranch?.trim() || null, canonicalKey: repositoryPathKey(canonical), gitIdentity, kind: gitIdentity ? 'git' : 'folder' };
|
|
156
|
+
return { ...input, workspacePath, workspacePaths, path: canonical, name: input.name?.trim(), integrationBranch: input.integrationBranch?.trim() || null, canonicalKey: repositoryPathKey(canonical), gitIdentity, kind: gitIdentity ? 'git' : 'folder' };
|
|
129
157
|
}
|
|
130
158
|
function assertDistinctRepositories(repositories) {
|
|
131
159
|
for (let i = 0; i < repositories.length; i++) {
|
|
132
160
|
for (let j = 0; j < i; j++) {
|
|
133
161
|
const a = repositories[i], b = repositories[j];
|
|
134
162
|
const overlapping = isRepositoryPathWithin(a.canonicalKey, b.canonicalKey) || isRepositoryPathWithin(b.canonicalKey, a.canonicalKey);
|
|
135
|
-
|
|
163
|
+
const container = (a.kind === 'folder' && b.kind === 'git' && isRepositoryPathWithin(a.canonicalKey, b.canonicalKey)) || (b.kind === 'folder' && a.kind === 'git' && isRepositoryPathWithin(b.canonicalKey, a.canonicalKey));
|
|
164
|
+
if ((overlapping && !container) || (a.gitIdentity !== null && a.gitIdentity === b.gitIdentity)) {
|
|
136
165
|
throw new RepositoryValidationError('Repositories in one project must have distinct, non-overlapping roots and Git identities', 'duplicate_repository', 409);
|
|
137
166
|
}
|
|
138
167
|
}
|
|
@@ -155,3 +184,23 @@ function resolveRepositoryProject(projects, inputPath, projectId) {
|
|
|
155
184
|
}
|
|
156
185
|
return selected.sort((a, b) => b.path.length - a.path.length)[0];
|
|
157
186
|
}
|
|
187
|
+
/** A launch may narrow registered code workspaces, never invent new targets. */
|
|
188
|
+
function validateWorkspaceSelection(project, value, repositoryIds) {
|
|
189
|
+
if (value === undefined)
|
|
190
|
+
return undefined;
|
|
191
|
+
if (!value || typeof value !== 'object' || Array.isArray(value) || !Object.keys(value).length)
|
|
192
|
+
throw new RepositoryValidationError('workspaceSelection must map repository IDs to nonempty workspace path arrays', 'invalid_workspace_selection');
|
|
193
|
+
const entries = Object.entries(value).map(([id, paths]) => {
|
|
194
|
+
if (!repositoryIds.includes(id))
|
|
195
|
+
throw new RepositoryValidationError('Workspace selection refers to an unselected repository', 'invalid_workspace_selection');
|
|
196
|
+
const repository = resolveProjectRepository(project, id);
|
|
197
|
+
if (!Array.isArray(paths) || !paths.length || paths.some(item => typeof item !== 'string' || !item.trim()))
|
|
198
|
+
throw new RepositoryValidationError('Select at least one code workspace per repository', 'invalid_workspace_selection');
|
|
199
|
+
const allowed = repository.workspacePaths ?? (repository.workspacePath ? [repository.workspacePath] : [repository.path]);
|
|
200
|
+
const selected = paths.map(item => canonicalRepositoryPath(path_1.default.resolve(repository.path, item)));
|
|
201
|
+
if (new Set(selected.map(repositoryPathKey)).size !== selected.length || selected.some(item => !allowed.some(root => repositoryPathKey(root) === repositoryPathKey(item))))
|
|
202
|
+
throw new RepositoryValidationError('Select only registered code workspace paths', 'invalid_workspace_selection');
|
|
203
|
+
return [id, selected];
|
|
204
|
+
});
|
|
205
|
+
return Object.fromEntries(entries);
|
|
206
|
+
}
|
|
@@ -347,7 +347,7 @@ function formatDescriptionWithCriteria(body, criteria) {
|
|
|
347
347
|
* Order:
|
|
348
348
|
* 1. `models.defaults.model` from the install-config.yaml (per-project HOME
|
|
349
349
|
* dir), if it parses AND is in the provider allow-list.
|
|
350
|
-
* 2. Provider default from `PROVIDER_DEFAULT_MODEL` (`sonnet` / `gpt-
|
|
350
|
+
* 2. Provider default from `PROVIDER_DEFAULT_MODEL` (`sonnet` / `gpt-6.1-sol`).
|
|
351
351
|
*
|
|
352
352
|
* Logs a warning when the configured value exists but is not valid for the
|
|
353
353
|
* project's provider.
|
|
@@ -352,9 +352,11 @@ function registerLoopRunRoutes(deps) {
|
|
|
352
352
|
effort = globalAgentDefaults.pipelineEffort;
|
|
353
353
|
}
|
|
354
354
|
let repositoryIds;
|
|
355
|
+
let workspaceSelection;
|
|
355
356
|
const primary = (0, project_repositories_1.getProjectRepositories)(c.project).find((repository) => repository.isPrimary);
|
|
356
357
|
try {
|
|
357
358
|
repositoryIds = (0, project_repositories_1.validateTicketRepositoryIds)(c.project, body.repositoryIds) ?? [primary.id];
|
|
359
|
+
workspaceSelection = (0, project_repositories_1.validateWorkspaceSelection)(c.project, body.workspaceSelection, repositoryIds);
|
|
358
360
|
(0, loop_graph_1.assertLoopShellRepositoryScope)(loop.graph, repositoryIds);
|
|
359
361
|
(0, process_admission_1.assertProcessAdmission)(c.project.id);
|
|
360
362
|
}
|
|
@@ -386,7 +388,7 @@ function registerLoopRunRoutes(deps) {
|
|
|
386
388
|
try {
|
|
387
389
|
let prDeliveryId;
|
|
388
390
|
const ids = await (0, multi_repo_execution_1.launchMultiRepositoryRail)({
|
|
389
|
-
ctx: c, railIndex: rail.railIndex, ticketIds: [], repositoryIds, scope: 'all',
|
|
391
|
+
ctx: c, railIndex: rail.railIndex, ticketIds: [], repositoryIds, workspaceSelection, scope: 'all',
|
|
390
392
|
loopId, loopName: loop.name, loopGraph: loop.graph, provider, model, effort,
|
|
391
393
|
onPrDeliveryCreated: (id) => { prDeliveryId = id; },
|
|
392
394
|
});
|
|
@@ -418,6 +420,7 @@ function registerLoopRunRoutes(deps) {
|
|
|
418
420
|
graph: loop.graph,
|
|
419
421
|
projectId: c.project.id,
|
|
420
422
|
repositoryId: primary.id,
|
|
423
|
+
workspacePaths: workspaceSelection?.[primary.id],
|
|
421
424
|
cwd: exec.cwd,
|
|
422
425
|
repoDir: exec.relocated ? exec.repoDir : undefined,
|
|
423
426
|
railIndex: null,
|