specrails-desktop 2.58.1 → 2.59.1
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-vAY9C71W.js} +1 -1
- package/client/dist/assets/AgentBrowserCapture-CpwjGXQm.js +1 -0
- package/client/dist/assets/AgentModeAnalyticsPane-BHeoJVxQ.js +2 -0
- package/client/dist/assets/AgentModeCodePane-B0Nf3YkN.js +2 -0
- package/client/dist/assets/{AgentModeJobsPane-DR_06oqH.js → AgentModeJobsPane-CpbIItY_.js} +1 -1
- package/client/dist/assets/{AgentsPage-BKIKjm_1.js → AgentsPage-BVJAFXn3.js} +3 -3
- package/client/dist/assets/{AnalyticsPage-069npRVo.js → AnalyticsPage-DDWees-K.js} +1 -1
- package/client/dist/assets/CodePage-CzV5tkvS.js +3 -0
- package/client/dist/assets/{DesktopAnalyticsPage-qtWAPM47.js → DesktopAnalyticsPage-DfRHNK7i.js} +1 -1
- package/client/dist/assets/DocsDialog-BxduBYbh.js +11 -0
- package/client/dist/assets/DocsPage-DOVeRJn_.js +11 -0
- package/client/dist/assets/{ExportDropdown-BcMpvV89.js → ExportDropdown-BnKVV8UK.js} +1 -1
- package/client/dist/assets/InteractiveJobComposer-Da9dvM-0.js +19 -0
- package/client/dist/assets/JobDetailModal-DhwKbZGe.js +1 -0
- package/client/dist/assets/JobDetailPage-MtPE-E_1.js +1 -0
- package/client/dist/assets/JobsPage-0vdlbNik.js +1 -0
- package/client/dist/assets/LoopBuilderPage-DCUHozXh.js +1 -0
- package/client/dist/assets/{LoopPreviewModal-je5pSCf-.js → LoopPreviewModal-Df-S8NGf.js} +1 -1
- package/client/dist/assets/LoopWindowSurface-sdb7QbSo.js +1 -0
- package/client/dist/assets/LoopsPage-CDfCC9Px.js +2 -0
- package/client/dist/assets/MinimizedChatsContext-DKlPf54Y.js +1 -0
- package/client/dist/assets/PluginsPage-iL0G3rQQ.js +2 -0
- package/client/dist/assets/ProjectSettingsDialog-DOkjT7xv.js +1 -0
- package/client/dist/assets/{RepositoryDeliveries-BiwJuS8r.js → RepositoryDeliveries-CJVpOUpZ.js} +1 -1
- package/client/dist/assets/RepositoryScopeSelector-BeM-2gIZ.js +2 -0
- package/client/dist/assets/ReviewPacketPage-EBCim2w_.js +4 -0
- package/client/dist/assets/TemplatePreviewModal-1-92UGwN.js +1 -0
- package/client/dist/assets/TicketDetailModalContext-CkVXuHxU.js +16 -0
- package/client/dist/assets/{Trans-D3AfDylf.js → Trans-CzhPjnjB.js} +1 -1
- package/client/dist/assets/agent--8QAfoXL.js +1 -0
- package/client/dist/assets/agent-3HxXkIWX.js +1 -0
- package/client/dist/assets/agent-BjttZLIl.js +1 -0
- package/client/dist/assets/agent-CpFCz32T.js +1 -0
- package/client/dist/assets/agent-DVnw6ybO.js +1 -0
- package/client/dist/assets/agent-Z5s_GTP0.js +1 -0
- package/client/dist/assets/agent-hv57-zyG.js +1 -0
- package/client/dist/assets/agent-xGrZ_aN1.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-DlQaXc7c.js} +1 -1
- package/client/dist/assets/{getTimezoneOffsetInMilliseconds-DOGj1Mfz.js → getTimezoneOffsetInMilliseconds-DxUFGcVq.js} +1 -1
- package/client/dist/assets/index-Bfunvi6w.css +2 -0
- package/client/dist/assets/index-Czov72g7.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-CbJvbRYx.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-BkBD3rxx.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-CYotH-9N.js} +1 -1
- package/client/dist/assets/subscriptionUsage-BH73RO86.js +1 -0
- package/client/dist/assets/subscriptionUsage-BWTeWlHx.js +1 -0
- package/client/dist/assets/subscriptionUsage-CBh82zVa.js +1 -0
- package/client/dist/assets/subscriptionUsage-CHcQ7lLe.js +1 -0
- package/client/dist/assets/subscriptionUsage-CdlE5Mg1.js +1 -0
- package/client/dist/assets/subscriptionUsage-DUPBmSQi.js +1 -0
- package/client/dist/assets/subscriptionUsage-DrlwrJ17.js +1 -0
- package/client/dist/assets/subscriptionUsage-EFu0mIK3.js +1 -0
- package/client/dist/assets/tauri-shell-DE6LfGPw.js +2 -0
- package/client/dist/assets/{useDesktop-0unHDBJJ.js → useDesktop-DpxVGfUC.js} +1 -1
- package/client/dist/assets/{useRuntimeRuns-BN1rhyEm.js → useRuntimeRuns-CfGRbhr9.js} +1 -1
- package/client/dist/assets/useSharedWebSocket-C17TGqT8.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/api-reference.md +1 -0
- package/docs/internals/core-runtime-updates.md +9 -2
- package/docs/internals/desktop-owned-workflows.md +144 -0
- package/docs/internals/mission-rail-cards.md +15 -0
- package/docs/internals/operations-runbook.md +26 -0
- package/docs/internals/programmatic-agent-runtime.md +34 -28
- package/docs/internals/project-builder.md +34 -11
- package/docs/internals/safe-pr-review-flow.md +25 -7
- package/docs/internals/source-map.md +53 -3
- package/docs/internals/spec-addenda.md +11 -5
- package/docs/internals/visual-design-audit.md +5 -0
- 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/core-update-manager.js +1 -1
- package/server/dist/db/migrations.js +9 -0
- 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 +169 -17
- 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 +10 -5
- package/server/dist/modules/delivery/runtime/rail-isolated-launch.js +66 -30
- package/server/dist/modules/delivery/runtime/rail-launch-parser.js +8 -0
- package/server/dist/modules/delivery/runtime/rail-pr-store.js +3 -3
- package/server/dist/modules/delivery/runtime/rail-relaunch.js +154 -0
- package/server/dist/modules/delivery/runtime/rails-router.js +157 -36
- package/server/dist/modules/delivery/runtime/review-packet.js +1 -1
- package/server/dist/modules/execution/runtime/revision-seed.js +6 -2
- 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-events.js +1 -1
- 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 +53 -0
- package/server/dist/modules/loops/runtime/loop-run-manager.js +30 -20
- 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 +123 -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 +12 -0
- package/server/dist/modules/subscription-usage/domain.js +104 -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/server/dist/worktree-manager.js +114 -6
- package/server/dist/worktree-overlay.js +4 -4
- 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
package/docs/tracking-cost.md
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# Tracking cost
|
|
2
2
|
|
|
3
|
-
specrails-desktop records AI CLI invocations across Claude, Codex, Gemini, and Kimi and surfaces the available metrics on one Analytics page per project. This guide walks through what's tracked, what's unavailable, and how to read the dashboard.
|
|
4
|
-
|
|
5
3
|
## What gets tracked
|
|
6
4
|
|
|
7
5
|
Six surfaces, all per-project:
|
|
@@ -15,18 +13,6 @@ Six surfaces, all per-project:
|
|
|
15
13
|
| **`smash`** | SMASH runs that break an epic spec into sub-specs |
|
|
16
14
|
| **`file-summary`** | Code-explorer AI summaries of individual files |
|
|
17
15
|
|
|
18
|
-
Each invocation row carries the provider, model, status, timestamps, duration,
|
|
19
|
-
and relevant ticket/conversation IDs. Token, USD-cost, and turn fields are
|
|
20
|
-
nullable because not every CLI reports them; Kimi leaves those values
|
|
21
|
-
unavailable rather than writing zero.
|
|
22
|
-
|
|
23
|
-
The surface list is app-wide, not a promise that every provider can spawn
|
|
24
|
-
every row type. Kimi records its agentic chats/Explore/rails/loops, but Quick
|
|
25
|
-
Spec, AI Edit, Contract Refine, SMASH/Re-SMASH, file summaries, construction
|
|
26
|
-
story, AI auto-title, Project Builder blueprint/milestone generation, Loop
|
|
27
|
-
Decider, and Agent Studio automation fail before spawn. Those rejected
|
|
28
|
-
requests create no invocation row; AI auto-title uses a deterministic fallback.
|
|
29
|
-
|
|
30
16
|
> Cost averages exclude `failed`/`aborted` rows, but those rows still count toward the total run count and the failure rate.
|
|
31
17
|
|
|
32
18
|
## Authoritative vs estimated cost
|
|
@@ -34,24 +20,16 @@ requests create no invocation row; AI auto-title uses a deterministic fallback.
|
|
|
34
20
|
Whether a cost figure is exact depends on the provider's CLI, not on which provider you picked:
|
|
35
21
|
|
|
36
22
|
- **Claude cost is provider-billed and authoritative** — the figure comes straight from the CLI's own usage report.
|
|
37
|
-
- **Codex
|
|
38
|
-
- **Kimi reports neither token counts nor a native USD-cost envelope** in its
|
|
39
|
-
stream. Specrails does not invent an estimate: cost and token cells stay
|
|
40
|
-
unavailable.
|
|
23
|
+
- **Codex do not report cost natively**, so the app **estimates** their cost from a local rate-card (`server/modules/accounting/runtime/pricing.ts`) using the captured token counts. Estimated rows are flagged: they render with a `~` tilde in the raw table (hover for the tooltip — *"Estimated from local pricing table — this provider does not report cost natively"*) and feed an "includes ~$X estimated" footnote in the Hero.
|
|
41
24
|
|
|
42
25
|
On multi-provider projects, a **Provider breakdown** card splits spend across the project's installed engines so you can see authoritative vs estimated at a glance.
|
|
43
26
|
|
|
44
|
-
> **Fail-soft on unknown models.** The rate card lists specific models (the current Codex
|
|
27
|
+
> **Fail-soft on unknown models.** The rate card lists specific models (the current Codex catalogs). If an invocation uses a model that isn't in the table, the app **does not fabricate a number** — it stores no cost (`total_cost_usd` is left empty), so that row simply shows a blank cost rather than a misleading estimate.
|
|
45
28
|
|
|
46
29
|
## What's NOT tracked (intentionally)
|
|
47
30
|
|
|
48
|
-
- **Sidebar chat** — the general-purpose chat panel in the right sidebar. It spawns an AI process, including `kimi -p` when Kimi is selected, but isn't pipeline work, so the app excludes it from analytics by design.
|
|
49
31
|
- **Setup wizard** — the install/enrich flow when you add a project. It *does* spawn an AI CLI (a genuine model invocation), but it's an interactive one-time wizard rather than a repeatable pipeline job, so it's deliberately left uninstrumented.
|
|
50
32
|
|
|
51
|
-
If you want the absolute total of what an engine has cost you, your provider's
|
|
52
|
-
own console is the source of truth. This is especially important for Kimi,
|
|
53
|
-
whose CLI stream provides no billable usage envelope to Desktop.
|
|
54
|
-
|
|
55
33
|
## The Analytics page
|
|
56
34
|
|
|
57
35
|
Open **Analytics** from the project right sidebar.
|
|
@@ -75,13 +53,6 @@ The big number at the top is total spend in the selected period, with:
|
|
|
75
53
|
|
|
76
54
|
When the project has zero invocations in the period (e.g. you just started), the Hero shows "Tracking started YYYY-MM-DD" — the date of the first invocation ever recorded. There's no historical backfill; tracking begins at that first row.
|
|
77
55
|
|
|
78
|
-
### Provider breakdown
|
|
79
|
-
|
|
80
|
-
On multi-provider projects, a dedicated card splits available spend across the
|
|
81
|
-
project's installed engines. It is data-driven, so Kimi appears even though its
|
|
82
|
-
cost is unavailable. Estimated Codex/Gemini totals carry a `~` tilde. The card
|
|
83
|
-
is hidden on single-provider projects.
|
|
84
|
-
|
|
85
56
|
### Daily stacked timeline
|
|
86
57
|
|
|
87
58
|
A daily bar chart for the period, stacked by surface. It plots five series — **Jobs, Explore, Quick, Refine, and File summaries**. SMASH spend is still tracked and shows up elsewhere (the Hero per-surface bar, Top models, and the raw table), but it is not currently a separate bar in this chart. Days with zero activity are zero-filled so the x-axis stays regular.
|
|
@@ -176,8 +147,6 @@ For the cross-project view, open **Analytics** from the Arc sidebar on the left.
|
|
|
176
147
|
- [Customising the app](customizing.md) — set the budget, configure notifications.
|
|
177
148
|
- [Creating specs](creating-specs.md) — every spec you create adds rows to your analytics.
|
|
178
149
|
- [Using Codex](codex.md) — how cost works when you run a project on Codex (estimated, ~ tilde).
|
|
179
|
-
- [Using Gemini](gemini.md) — how cost works when you run a project on Gemini (also estimated, ~ tilde).
|
|
180
|
-
- [Using Kimi](kimi.md) — why Kimi duration/outcome are recorded while tokens and cost remain unavailable.
|
|
181
150
|
|
|
182
151
|
## Legacy workflow usage
|
|
183
152
|
|
package/package.json
CHANGED
|
@@ -51,16 +51,15 @@ function checkoutSubdirectory(directory) {
|
|
|
51
51
|
function registeredScope(checkout, sourcePath) {
|
|
52
52
|
if (!sourcePath)
|
|
53
53
|
return undefined;
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
(0, node_fs_1.realpathSync)(sourcePath);
|
|
55
|
+
const local = (0, node_path_1.relative)(checkout, sourcePath).split(node_path_1.sep).join('/');
|
|
56
|
+
const contained = local && local !== '..' && !local.startsWith('../') && !(0, node_path_1.isAbsolute)(local);
|
|
57
|
+
const prefix = checkoutSubdirectory(checkout) ? (contained ? local : '') : checkoutSubdirectory(sourcePath) || (contained ? local : '');
|
|
58
|
+
if (!prefix)
|
|
57
59
|
return undefined;
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
catch {
|
|
62
|
-
return undefined;
|
|
63
|
-
}
|
|
60
|
+
if (!(0, node_fs_1.statSync)((0, node_path_1.join)(checkout, ...prefix.split('/'))).isDirectory())
|
|
61
|
+
throw new Error('Selected code workspace is unavailable in the execution checkout');
|
|
62
|
+
return [prefix];
|
|
64
63
|
}
|
|
65
64
|
function withoutScope(context) {
|
|
66
65
|
return { ...context, repositories: context.repositories.map(({ scope: _scope, ...repository }) => repository) };
|
|
@@ -74,13 +73,17 @@ function prepareCoreExecution(input) {
|
|
|
74
73
|
throw new Error('Invalid Core execution run id');
|
|
75
74
|
const cwd = (0, node_fs_1.realpathSync)(input.cwd);
|
|
76
75
|
(0, core_update_state_1.assertWorkspaceCoreReady)(cwd);
|
|
77
|
-
const scoped = (repository, sourcePath) => {
|
|
78
|
-
const scope = registeredScope(repository.path, sourcePath);
|
|
79
|
-
|
|
76
|
+
const scoped = (repository, sourcePath, workspacePaths, selectedWorkspacePaths) => {
|
|
77
|
+
const scope = workspacePaths?.length ? [...new Set(workspacePaths.flatMap(workspace => registeredScope(repository.path, workspace) ?? []))] : registeredScope(repository.path, sourcePath);
|
|
78
|
+
if (selectedWorkspacePaths) {
|
|
79
|
+
const selectedScope = [...new Set(selectedWorkspacePaths.flatMap(workspace => registeredScope(repository.path, workspace) ?? []))];
|
|
80
|
+
return { ...repository, ...(selectedScope.length ? { scope: selectedScope } : {}), ...(scope?.length ? { registeredScope: scope } : {}) };
|
|
81
|
+
}
|
|
82
|
+
return scope?.length ? { ...repository, scope } : repository;
|
|
80
83
|
};
|
|
81
84
|
const repositories = manifest
|
|
82
|
-
? manifest.repositories.map(repo => scoped({ id: repo.repositoryId, name: repo.name, path: (0, node_fs_1.realpathSync)(repo.worktreePath), baseSha: repo.baseSha }, repo.sourcePath))
|
|
83
|
-
: [scoped({ id: run.repositoryId ?? (run.spec?.repositoryIds?.length === 1 ? run.spec.repositoryIds[0] : run.projectId ? `primary-${run.projectId}` : 'primary'), name: (0, node_path_1.basename)(input.repoDir ?? cwd), path: (0, node_fs_1.realpathSync)(input.repoDir ?? cwd) }, input.sourcePath)];
|
|
85
|
+
? manifest.repositories.map(repo => scoped({ id: repo.repositoryId, name: repo.name, path: (0, node_fs_1.realpathSync)(repo.worktreePath), baseSha: repo.baseSha }, repo.workspacePath ?? repo.sourcePath, repo.workspacePaths, repo.selectedWorkspacePaths))
|
|
86
|
+
: [scoped({ id: run.repositoryId ?? (run.spec?.repositoryIds?.length === 1 ? run.spec.repositoryIds[0] : run.projectId ? `primary-${run.projectId}` : 'primary'), name: (0, node_path_1.basename)(input.repoDir ?? cwd), path: (0, node_fs_1.realpathSync)(input.repoDir ?? cwd) }, input.sourcePath, input.workspacePaths, input.selectedWorkspacePaths)];
|
|
84
87
|
if (!repositories.length || repositories.some(repo => !SAFE_ID.test(repo.id)))
|
|
85
88
|
throw new Error('Invalid Core repository scope');
|
|
86
89
|
const artifactRepositoryId = manifest?.artifactRepositoryId ?? repositories[0].id;
|
|
@@ -127,7 +130,13 @@ function prepareCoreExecution(input) {
|
|
|
127
130
|
}
|
|
128
131
|
const env = { ...input.env, SPECRAILS_EXECUTION_CONTEXT: contextPath };
|
|
129
132
|
const promptPrefix = run.verificationStep ? coreVerificationContext(contextPath, cwd, env, run.runId) : '';
|
|
130
|
-
|
|
133
|
+
const frozen = JSON.parse((0, node_fs_1.readFileSync)(contextPath, 'utf8'));
|
|
134
|
+
const workspaceBriefing = [
|
|
135
|
+
'Code workspaces frozen for this run (the project folder and the internal agent workspace are not test targets):',
|
|
136
|
+
...frozen.repositories.map(repo => JSON.stringify({ repositoryId: repo.id, name: repo.name, checkout: repo.path, workspaces: (repo.scope ?? ['.']).map(scope => (0, node_path_1.resolve)(repo.path, scope)) })),
|
|
137
|
+
'Run tests, builds and other checks in these code workspace directories. For a manual shell command, set its cwd explicitly or cd to the exact workspace first. Do not use the parent project directory or another workspace as a fallback. For multiple workspaces, verify each selected workspace separately. If a workspace or its test command is unavailable, report the blocker.',
|
|
138
|
+
].join('\n');
|
|
139
|
+
return { env, contextPath, promptPrefix: [workspaceBriefing, promptPrefix].filter(Boolean).join('\n\n'), workspaceBriefing };
|
|
131
140
|
}
|
|
132
141
|
/** Ask the installed Core runtime to validate its own receipts. Desktop never
|
|
133
142
|
* treats an agent-authored summary or a JSON 'pass' field as verification.
|
|
@@ -73,7 +73,7 @@ class CoreUpdateManager {
|
|
|
73
73
|
this.latestVersion != null &&
|
|
74
74
|
currentVersion != null &&
|
|
75
75
|
((0, semver_lite_1.isNewer)(this.latestVersion, currentVersion) || selected.error !== null && this.latestVersion === currentVersion) &&
|
|
76
|
-
|
|
76
|
+
(0, core_package_1.isSupportedCoreVersion)(this.latestVersion);
|
|
77
77
|
return {
|
|
78
78
|
available: this.isAvailable(),
|
|
79
79
|
currentVersion,
|
|
@@ -1608,6 +1608,15 @@ const MIGRATIONS = [
|
|
|
1608
1608
|
(db) => {
|
|
1609
1609
|
db.exec(`UPDATE rails SET mode='implement' WHERE mode IN ('batch-implement','batch')`);
|
|
1610
1610
|
},
|
|
1611
|
+
// Migration 71: source-bound rail relaunch. NULL preserves historical rows;
|
|
1612
|
+
// new attempts freeze their admitted launch options before agent execution.
|
|
1613
|
+
(db) => {
|
|
1614
|
+
for (const table of ['rail_pr_deliveries', 'loop_runs']) {
|
|
1615
|
+
const columns = db.prepare(`PRAGMA table_info(${table})`).all();
|
|
1616
|
+
if (!columns.some(column => column.name === 'launch_config_json'))
|
|
1617
|
+
db.exec(`ALTER TABLE ${table} ADD COLUMN launch_config_json TEXT`);
|
|
1618
|
+
}
|
|
1619
|
+
},
|
|
1611
1620
|
];
|
|
1612
1621
|
function applyMigrations(db) {
|
|
1613
1622
|
// Ensure the migrations table exists (migration 1 creates it, but we need
|
|
@@ -675,6 +675,14 @@ function applyDesktopMigrations(db) {
|
|
|
675
675
|
UPDATE loops SET published_graph = graph WHERE status = 'published' AND published_graph IS NULL;
|
|
676
676
|
`);
|
|
677
677
|
},
|
|
678
|
+
// 32: explicit code workspace; old memberships continue to use their root.
|
|
679
|
+
() => {
|
|
680
|
+
const columns = db.prepare('PRAGMA table_info(project_repositories)').all().map(column => column.name);
|
|
681
|
+
if (!columns.includes('workspace_path'))
|
|
682
|
+
db.exec('ALTER TABLE project_repositories ADD COLUMN workspace_path TEXT');
|
|
683
|
+
if (!columns.includes('workspace_paths'))
|
|
684
|
+
db.exec('ALTER TABLE project_repositories ADD COLUMN workspace_paths TEXT');
|
|
685
|
+
},
|
|
678
686
|
];
|
|
679
687
|
(0, sqlite_migrations_1.applyNumberedMigrations)(db, migrations);
|
|
680
688
|
}
|
|
@@ -726,15 +734,15 @@ function addProject(db, project) {
|
|
|
726
734
|
const provider = project.provider ?? providers[0];
|
|
727
735
|
// Low-level callers may register unavailable legacy roots. HTTP validates all
|
|
728
736
|
// roots first; membership and project rows are still committed atomically here.
|
|
729
|
-
const roots = [(0, project_repositories_1.inspectRepositoryPath)({ path: project.path, name: project.name },
|
|
737
|
+
const roots = [(0, project_repositories_1.inspectRepositoryPath)({ path: project.path, name: project.name, workspacePaths: project.workspacePaths }, project.workspacePaths !== undefined), ...(project.repositories ?? []).map((input) => (0, project_repositories_1.inspectRepositoryPath)(input))];
|
|
730
738
|
(0, project_repositories_1.assertDistinctRepositories)(roots);
|
|
731
739
|
db.transaction(() => {
|
|
732
740
|
db.prepare(`
|
|
733
741
|
INSERT INTO projects (id, slug, name, path, db_path, provider, providers)
|
|
734
742
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
735
743
|
`).run(project.id, project.slug, project.name, project.path, dbPath, provider, JSON.stringify(providers));
|
|
736
|
-
const insert = db.prepare(`INSERT INTO project_repositories (id, project_id, name, path, canonical_key, git_identity, is_primary, kind, integration_branch) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
737
|
-
roots.forEach((root, index) => insert.run(index === 0 ? `primary-${project.id}` : (0, crypto_1.randomUUID)(), project.id, root.name ?? path_1.default.basename(root.path), index === 0 ? project.path : root.path, root.canonicalKey, root.gitIdentity, index === 0 ? 1 : 0, root.kind, root.integrationBranch ?? null));
|
|
744
|
+
const insert = db.prepare(`INSERT INTO project_repositories (id, project_id, name, path, canonical_key, git_identity, is_primary, kind, integration_branch, workspace_path, workspace_paths) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
745
|
+
roots.forEach((root, index) => insert.run(index === 0 ? `primary-${project.id}` : (0, crypto_1.randomUUID)(), project.id, root.name ?? path_1.default.basename(root.path), index === 0 ? project.path : root.path, root.canonicalKey, root.gitIdentity, index === 0 ? 1 : 0, root.kind, root.integrationBranch ?? null, root.workspacePath ?? null, root.workspacePaths ? JSON.stringify(root.workspacePaths) : null));
|
|
738
746
|
})();
|
|
739
747
|
return getProject(db, project.id);
|
|
740
748
|
}
|
|
@@ -742,7 +750,9 @@ function listProjectRepositories(db, projectId) {
|
|
|
742
750
|
return db.prepare('SELECT * FROM project_repositories WHERE project_id = ? ORDER BY is_primary DESC, added_at, rowid').all(projectId).map((row) => ({
|
|
743
751
|
id: row.id, projectId: row.project_id, name: row.name, path: row.path,
|
|
744
752
|
isPrimary: row.is_primary === 1, kind: row.kind, integrationBranch: row.integration_branch,
|
|
745
|
-
addedAt: row.added_at, available: (0, project_repositories_1.repositoryAvailable)(row.path),
|
|
753
|
+
addedAt: row.added_at, available: (0, project_repositories_1.repositoryAvailable)(row.path) && (row.workspace_paths ? JSON.parse(row.workspace_paths).every(project_repositories_1.repositoryAvailable) : (0, project_repositories_1.repositoryAvailable)(row.workspace_path ?? row.path)),
|
|
754
|
+
...(row.workspace_path ? { workspacePath: row.workspace_path } : {}),
|
|
755
|
+
...(row.workspace_paths ? { workspacePaths: JSON.parse(row.workspace_paths) } : {}),
|
|
746
756
|
}));
|
|
747
757
|
}
|
|
748
758
|
function addProjectRepository(db, projectId, input) {
|
|
@@ -752,8 +762,8 @@ function addProjectRepository(db, projectId, input) {
|
|
|
752
762
|
const existing = listProjectRepositories(db, projectId).map((repo) => (0, project_repositories_1.inspectRepositoryPath)(repo, false));
|
|
753
763
|
(0, project_repositories_1.assertDistinctRepositories)([...existing, root]);
|
|
754
764
|
const id = (0, crypto_1.randomUUID)();
|
|
755
|
-
db.prepare(`INSERT INTO project_repositories (id, project_id, name, path, canonical_key, git_identity, kind, integration_branch) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
756
|
-
.run(id, projectId, root.name ?? path_1.default.basename(root.path), root.path, root.canonicalKey, root.gitIdentity, root.kind, root.integrationBranch ?? null);
|
|
765
|
+
db.prepare(`INSERT INTO project_repositories (id, project_id, name, path, canonical_key, git_identity, kind, integration_branch, workspace_path, workspace_paths) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
766
|
+
.run(id, projectId, root.name ?? path_1.default.basename(root.path), root.path, root.canonicalKey, root.gitIdentity, root.kind, root.integrationBranch ?? null, root.workspacePath ?? null, root.workspacePaths ? JSON.stringify(root.workspacePaths) : null);
|
|
757
767
|
return listProjectRepositories(db, projectId).find((repo) => repo.id === id);
|
|
758
768
|
}
|
|
759
769
|
function updateProjectRepository(db, projectId, repositoryId, input) {
|
|
@@ -763,15 +773,18 @@ function updateProjectRepository(db, projectId, repositoryId, input) {
|
|
|
763
773
|
throw new project_repositories_1.RepositoryValidationError('Repository does not belong to this project', 'repository_not_found', 404);
|
|
764
774
|
if (current.isPrimary && input.path !== undefined)
|
|
765
775
|
throw new project_repositories_1.RepositoryValidationError('The primary project path cannot be relocated here', 'primary_repository_protected', 409);
|
|
766
|
-
const next = { ...current, ...input
|
|
767
|
-
|
|
776
|
+
const next = { ...current, ...input,
|
|
777
|
+
...(input.workspacePaths !== undefined && input.workspacePath === undefined ? { workspacePath: null } : {}),
|
|
778
|
+
...(input.workspacePath !== undefined && input.workspacePaths === undefined ? { workspacePaths: null } : {}),
|
|
779
|
+
};
|
|
780
|
+
const root = (0, project_repositories_1.inspectRepositoryPath)(next, input.path !== undefined || input.workspacePath !== undefined || input.workspacePaths !== undefined);
|
|
768
781
|
if (input.path === undefined && !(0, project_repositories_1.repositoryAvailable)(current.path)) {
|
|
769
782
|
root.kind = current.kind;
|
|
770
783
|
root.gitIdentity = db.prepare('SELECT git_identity FROM project_repositories WHERE id = ?').get(repositoryId).git_identity;
|
|
771
784
|
}
|
|
772
785
|
(0, project_repositories_1.assertDistinctRepositories)([...repositories.filter((repo) => repo.id !== repositoryId).map((repo) => (0, project_repositories_1.inspectRepositoryPath)(repo, false)), root]);
|
|
773
|
-
db.prepare('UPDATE project_repositories SET name = ?, path = ?, canonical_key = ?, git_identity = ?, kind = ?, integration_branch = ? WHERE project_id = ? AND id = ?')
|
|
774
|
-
.run(root.name ?? current.name, input.path === undefined ? current.path : root.path, root.canonicalKey, root.gitIdentity, root.kind, root.integrationBranch ?? null, projectId, repositoryId);
|
|
786
|
+
db.prepare('UPDATE project_repositories SET name = ?, path = ?, canonical_key = ?, git_identity = ?, kind = ?, integration_branch = ?, workspace_path = ?, workspace_paths = ? WHERE project_id = ? AND id = ?')
|
|
787
|
+
.run(root.name ?? current.name, input.path === undefined ? current.path : root.path, root.canonicalKey, root.gitIdentity, root.kind, root.integrationBranch ?? null, root.workspacePath ?? null, root.workspacePaths ? JSON.stringify(root.workspacePaths) : null, projectId, repositoryId);
|
|
775
788
|
return listProjectRepositories(db, projectId).find((repo) => repo.id === repositoryId);
|
|
776
789
|
}
|
|
777
790
|
/** Removes the association only. Operational reference guards belong to ProjectRegistry. */
|
|
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.createDesktopRouter = createDesktopRouter;
|
|
7
|
+
const http_1 = require("./modules/subscription-usage/adapters/http");
|
|
7
8
|
const runtime_role_prompts_router_1 = require("./runtime-role-prompts-router");
|
|
8
9
|
const express_1 = require("express");
|
|
9
10
|
const agent_runtime_settings_1 = require("./modules/agent-runtime/runtime/agent-runtime-settings");
|
|
@@ -319,8 +320,10 @@ function publicWebhook(row) {
|
|
|
319
320
|
const { secret: _secret, ...rest } = row;
|
|
320
321
|
return { ...rest, hasSecret: row.secret.length > 0 };
|
|
321
322
|
}
|
|
322
|
-
function createDesktopRouter(registry, broadcast) {
|
|
323
|
+
function createDesktopRouter(registry, broadcast, usageService) {
|
|
323
324
|
const router = (0, express_1.Router)();
|
|
325
|
+
if (usageService)
|
|
326
|
+
(0, http_1.registerUsageRoutes)(router, usageService);
|
|
324
327
|
// Loops (global, cross-project library) — /api/loops*. Registered here so the
|
|
325
328
|
// routes live on the global `/api` router (loops are NOT project-scoped). The
|
|
326
329
|
// A loop is "running" iff any project's per-project DB has an in-flight
|
|
@@ -476,7 +479,7 @@ function createDesktopRouter(registry, broadcast) {
|
|
|
476
479
|
});
|
|
477
480
|
// POST /api/projects — register a new project by path
|
|
478
481
|
router.post('/projects', async (req, res) => {
|
|
479
|
-
const { path: projectPath, name, provider, providers: providersRaw, repositories: repositoriesRaw } = req.body ?? {};
|
|
482
|
+
const { path: projectPath, name, provider, providers: providersRaw, repositories: repositoriesRaw, workspacePaths } = req.body ?? {};
|
|
480
483
|
if (!projectPath || typeof projectPath !== 'string') {
|
|
481
484
|
res.status(400).json({ error: 'path is required' });
|
|
482
485
|
return;
|
|
@@ -573,7 +576,7 @@ function createDesktopRouter(registry, broadcast) {
|
|
|
573
576
|
if (repositoriesRaw !== undefined && !Array.isArray(repositoriesRaw))
|
|
574
577
|
throw new project_repositories_1.RepositoryValidationError('repositories must be an array');
|
|
575
578
|
repositories = (repositoriesRaw ?? []).map((input) => (0, project_repositories_1.inspectRepositoryPath)(input));
|
|
576
|
-
const primary = (0, project_repositories_1.inspectRepositoryPath)({ path: canonicalPath });
|
|
579
|
+
const primary = (0, project_repositories_1.inspectRepositoryPath)({ path: canonicalPath, workspacePaths });
|
|
577
580
|
(0, project_repositories_1.assertDistinctRepositories)([primary, ...repositories.map((input) => (0, project_repositories_1.inspectRepositoryPath)(input))]);
|
|
578
581
|
if (repositories.some((repository) => !isPathSafe(repository.path)))
|
|
579
582
|
throw new project_repositories_1.RepositoryValidationError('Registering system directories is not allowed');
|
|
@@ -609,6 +612,7 @@ function createDesktopRouter(registry, broadcast) {
|
|
|
609
612
|
provider: providers[0],
|
|
610
613
|
providers,
|
|
611
614
|
repositories,
|
|
615
|
+
workspacePaths,
|
|
612
616
|
});
|
|
613
617
|
broadcast({
|
|
614
618
|
type: 'desktop.project_added',
|
|
@@ -681,7 +685,7 @@ function createDesktopRouter(registry, broadcast) {
|
|
|
681
685
|
try {
|
|
682
686
|
const body = req.body ?? {};
|
|
683
687
|
const input = {};
|
|
684
|
-
for (const key of ['path', 'name', 'integrationBranch'])
|
|
688
|
+
for (const key of ['path', 'name', 'integrationBranch', 'workspacePath', 'workspacePaths'])
|
|
685
689
|
if (Object.prototype.hasOwnProperty.call(body, key))
|
|
686
690
|
Object.assign(input, { [key]: body[key] });
|
|
687
691
|
if (input.path !== undefined) {
|
package/server/dist/index.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
// Note: the pkg-binary native-addon hijacks (better_sqlite3.node, node-pty, pty.node)
|
|
3
|
-
// used to live here but had to move to an esbuild `banner.js` in scripts/build-sidecar.mjs
|
|
4
|
-
// so they run BEFORE esbuild's top-of-bundle `require('node-pty')` statement.
|
|
5
|
-
// See the banner in that script for the actual patches.
|
|
6
2
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
7
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
8
4
|
};
|
|
9
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const composition_1 = require("./modules/subscription-usage/runtime/composition");
|
|
7
|
+
// Note: the pkg-binary native-addon hijacks (better_sqlite3.node, node-pty, pty.node)
|
|
8
|
+
// used to live here but had to move to an esbuild `banner.js` in scripts/build-sidecar.mjs
|
|
9
|
+
// so they run BEFORE esbuild's top-of-bundle `require('node-pty')` statement.
|
|
10
|
+
// See the banner in that script for the actual patches.
|
|
10
11
|
const http_1 = __importDefault(require("http"));
|
|
11
12
|
const path_1 = __importDefault(require("path"));
|
|
12
13
|
const fs_1 = __importDefault(require("fs"));
|
|
@@ -275,6 +276,7 @@ function broadcast(msg) {
|
|
|
275
276
|
let _getProjectCount = () => 0;
|
|
276
277
|
/** Captured by the Super-mode bootstrap block so graceful shutdown can tear down every
|
|
277
278
|
* project's spawners (rail/chat children) instead of orphaning them. */
|
|
279
|
+
let _usageService = null;
|
|
278
280
|
let _registry = null;
|
|
279
281
|
/** The mobile companion gateway (off by default); torn down on shutdown. */
|
|
280
282
|
let _mobileGateway = null;
|
|
@@ -672,7 +674,9 @@ function applyPtyWsRateLimiting(ws) {
|
|
|
672
674
|
// GET /api/projects, DELETE /api/projects/:id) are handled here, while
|
|
673
675
|
// everything else under /api/projects/:projectId/* falls through to the
|
|
674
676
|
// project router.
|
|
675
|
-
|
|
677
|
+
const usageService = (0, composition_1.createDesktopUsageService)();
|
|
678
|
+
_usageService = usageService;
|
|
679
|
+
app.use('/api', (0, desktop_router_1.createDesktopRouter)(registry, broadcast, usageService));
|
|
676
680
|
// Per-project routes under /api/projects/:projectId/*
|
|
677
681
|
app.use('/api/projects', (0, project_router_1.createProjectRouter)(registry));
|
|
678
682
|
// Return 410 Gone for the old per-project hook endpoint in Super mode
|
|
@@ -780,6 +784,7 @@ async function shutdown() {
|
|
|
780
784
|
// skip Agent Chat, and Headroom must remain available until all routed AI
|
|
781
785
|
// children have received their termination signal.
|
|
782
786
|
await Promise.allSettled([
|
|
787
|
+
Promise.resolve().then(() => _usageService?.dispose()),
|
|
783
788
|
Promise.resolve().then(() => _registry?.shutdown()),
|
|
784
789
|
Promise.resolve().then(() => (0, terminal_manager_1.getTerminalManager)().shutdown()),
|
|
785
790
|
Promise.resolve().then(() => _mobileGateway?.stop()),
|
package/server/dist/mcp/guide.js
CHANGED
|
@@ -28,6 +28,7 @@ Codex, Gemini, Kimi Code) to implement specs.
|
|
|
28
28
|
\`specrails_projects(get)\` includes the repo's absolute path and availability.
|
|
29
29
|
A registered project with an unavailable database still exists; never create
|
|
30
30
|
a duplicate or interpret unavailable data as an empty backlog.
|
|
31
|
+
- **Code workspace**: a configured code directory inside a repository. A project can have one or many. Discover \`workspacePaths\` with \`specrails_projects(get/repositories)\`; configure them with \`repository_add/repository_update\` (write permission), or register them through \`specrails_setup(add_project)\`. Independent Git roots are separate repository memberships. Launch rails or standalone loops with \`workspaceSelection\` mapping repository IDs to registered paths; omission selects all configured workspaces. Tests run within the selected directories, never implicitly in the project parent.
|
|
31
32
|
- **Repository**: a stable membership within a project, addressed by \`repositoryId\`.
|
|
32
33
|
Discover the inventory in \`specrails_projects(get)\` or \`specrails_context(overview)\`.
|
|
33
34
|
A member is a Git repository or a non-Git context folder. File paths are relative
|
|
@@ -138,7 +138,7 @@ function jobsTools() {
|
|
|
138
138
|
text: zod_1.z.string().optional().describe('Prompt text for interactive_turn'),
|
|
139
139
|
chatId: zod_1.z.string().optional().describe('Conversation id for background_logs ownership. background_start/background_kill ignore this field and use the authenticated in-app capability.'),
|
|
140
140
|
repositoryId: zod_1.z.string().min(1).optional().describe('spawn/background_start: repository membership ID; required when the project has several repositories. spawn accepts only the primary repository; use specrails_rails launch or specrails_loops run for other repositories.'),
|
|
141
|
-
cwd: zod_1.z.string().optional().describe('Optional cwd for background_start; resolved inside the selected repository root'),
|
|
141
|
+
cwd: zod_1.z.string().optional().describe('Optional cwd for background_start; resolved inside the selected repository root. For tests/dev servers, discover workspacePaths with specrails_projects get and pass the intended code workspace cwd explicitly'),
|
|
142
142
|
pid: zod_1.z.number().int().positive().optional().describe('Background process pid for background_logs/background_kill'),
|
|
143
143
|
processId: zod_1.z.string().min(1).max(200).optional().describe('Background execution UUID returned by background_start/list. Pass with pid on logs/kill to avoid targeting a reused PID.'),
|
|
144
144
|
includeFinished: zod_1.z.boolean().optional().describe('background_list: include persisted completed/disconnected executions (default true). Active applications are listed first; use limit/offset for older history.'),
|
|
@@ -106,6 +106,7 @@ function loopsTools() {
|
|
|
106
106
|
.describe('Project id (run / run_get only — defaults to the active project; ignored by every other action)'),
|
|
107
107
|
loopId: zod_1.z.string().optional().describe('Loop id (for get / update / publish / unpublish / duplicate / restore_builtin / delete / run — must be Published for run). Built-ins use their factory id, e.g. factory:implement'),
|
|
108
108
|
repositoryIds: zod_1.z.array(zod_1.z.string().min(1)).min(1).max(50).optional().describe('run only: repository memberships to include in one coordinated standalone execution; omission retains primary-only behavior.'),
|
|
109
|
+
workspaceSelection: zod_1.z.record(zod_1.z.string(), zod_1.z.array(zod_1.z.string().min(1)).min(1).max(50)).optional().describe('run only: selected registered code workspace paths per selected repository ID. Omission uses all configured workspaces.'),
|
|
109
110
|
loopRunId: zod_1.z.string().optional().describe('Loop run id (for run_get — returned by run)'),
|
|
110
111
|
constantId: zod_1.z.string().optional().describe('Constant id (for constant_update / constant_delete)'),
|
|
111
112
|
templateId: zod_1.z.string().optional().describe('Template id (for from_template)'),
|
|
@@ -270,6 +271,7 @@ function loopsTools() {
|
|
|
270
271
|
const r = await (0, types_1.apiCall)(ctx, 'POST', `${base}/loop-runs`, {
|
|
271
272
|
loopId,
|
|
272
273
|
...(args.repositoryIds === undefined ? {} : { repositoryIds: args.repositoryIds }),
|
|
274
|
+
...(args.workspaceSelection === undefined ? {} : { workspaceSelection: args.workspaceSelection }),
|
|
273
275
|
...(provider !== undefined ? { provider } : {}),
|
|
274
276
|
...(model !== undefined ? { model } : {}),
|
|
275
277
|
...(effort !== undefined ? { reasoning_effort: effort } : {}),
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
3
|
exports.metaTools = metaTools;
|
|
7
4
|
const zod_1 = require("zod");
|
|
8
|
-
const
|
|
5
|
+
const project_repositories_1 = require("../../project-repositories");
|
|
9
6
|
const zod_json_schema_compat_js_1 = require("@modelcontextprotocol/sdk/server/zod-json-schema-compat.js");
|
|
10
7
|
const guide_1 = require("../guide");
|
|
11
8
|
const types_1 = require("./types");
|
|
@@ -162,9 +159,11 @@ function metaTools(getSpecs) {
|
|
|
162
159
|
handler: (ctx, args) => {
|
|
163
160
|
if (args.path !== undefined && args.projectId !== undefined)
|
|
164
161
|
throw new Error('Provide either path or projectId, not both.');
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
162
|
+
const key = args.path ? (0, project_repositories_1.repositoryPathKey)((0, project_repositories_1.canonicalRepositoryPath)(String(args.path))) : undefined;
|
|
163
|
+
const matches = key ? ctx.registry.listProjects().filter(project => (0, project_repositories_1.getProjectRepositories)(project).some(repository => [repository.path, ...(repository.workspacePaths ?? (repository.workspacePath ? [repository.workspacePath] : []))].some(root => (0, project_repositories_1.repositoryPathKey)((0, project_repositories_1.canonicalRepositoryPath)(root)) === key))) : [];
|
|
164
|
+
if (matches.length > 1)
|
|
165
|
+
throw new Error('Ambiguous project path; provide an explicit projectId.');
|
|
166
|
+
const project = key ? matches[0] : typeof args.projectId === 'string' ? ctx.registry.getProjectRow(args.projectId) : undefined;
|
|
168
167
|
if (args.path && !project)
|
|
169
168
|
throw new Error(`No project registered at path "${args.path}".`);
|
|
170
169
|
if (args.projectId && !project)
|
|
@@ -25,17 +25,42 @@ function projectsTools() {
|
|
|
25
25
|
name: 'specrails_projects',
|
|
26
26
|
title: 'Projects',
|
|
27
27
|
description: 'List and inspect registered Specrails projects, resolve a project by filesystem path, or unregister one. ' +
|
|
28
|
-
'Actions: list, get, resolve (by path), unregister (destructive — removes the project and its workspace).',
|
|
28
|
+
'Actions: list, get, resolve (by project/repository/workspace path), repositories (inventory), repository_add / repository_update (write, configure registered code workspacePaths), repository_remove (destructive), unregister (destructive — removes the project and its workspace).',
|
|
29
29
|
hintTier: 'read',
|
|
30
|
-
tier: (args) => (args.action
|
|
30
|
+
tier: (args) => ['unregister', 'repository_remove'].includes(String(args.action)) ? 'destructive' : ['repository_add', 'repository_update'].includes(String(args.action)) ? 'write' : 'read',
|
|
31
31
|
inputSchema: {
|
|
32
|
-
action: zod_1.z.enum(['list', 'get', 'resolve', 'unregister']).describe('Operation to perform'),
|
|
33
|
-
projectId: zod_1.z.string().optional().describe('Project id
|
|
34
|
-
path: zod_1.z.string().optional().describe('Filesystem path
|
|
32
|
+
action: zod_1.z.enum(['list', 'get', 'resolve', 'repositories', 'repository_add', 'repository_update', 'repository_remove', 'unregister']).describe('Operation to perform'),
|
|
33
|
+
projectId: zod_1.z.string().optional().describe('Project id; defaults to the active project for get and repository operations; required for unregister'),
|
|
34
|
+
path: zod_1.z.string().optional().describe('Filesystem path for resolve or repository_add/update; repository root retains its Git/delivery identity'),
|
|
35
|
+
repositoryId: zod_1.z.string().min(1).optional().describe('Required for repository_update/remove; discover from repositories/get'),
|
|
36
|
+
name: zod_1.z.string().min(1).optional().describe('Repository display name'),
|
|
37
|
+
integrationBranch: zod_1.z.string().nullable().optional().describe('Repository integration branch; null clears it'),
|
|
38
|
+
workspacePaths: zod_1.z.array(zod_1.z.string().min(1)).min(1).max(50).nullable().optional().describe('repository_add/update: code directories inside the repository, absolute or root-relative. null resets to repository root. Independent Git roots need separate memberships. Changes are rejected while active runs depend on this scope.'),
|
|
35
39
|
},
|
|
36
40
|
handler: (ctx, args) => {
|
|
37
41
|
const action = args.action;
|
|
38
42
|
switch (action) {
|
|
43
|
+
case 'repositories':
|
|
44
|
+
return (0, types_1.apiCall)(ctx, 'GET', `${(0, types_1.projectPath)(ctx, args.projectId)}/repositories`);
|
|
45
|
+
case 'repository_add':
|
|
46
|
+
case 'repository_update':
|
|
47
|
+
case 'repository_remove': {
|
|
48
|
+
const base = `${(0, types_1.projectPath)(ctx, args.projectId)}/repositories`;
|
|
49
|
+
const id = args.repositoryId;
|
|
50
|
+
if (action !== 'repository_add' && !id)
|
|
51
|
+
throw new Error(`${action} requires repositoryId.`);
|
|
52
|
+
if (action === 'repository_remove')
|
|
53
|
+
return (0, types_1.apiCall)(ctx, 'DELETE', `${base}/${encodeURIComponent(id)}`);
|
|
54
|
+
if (action === 'repository_add' && !args.path)
|
|
55
|
+
throw new Error('repository_add requires path.');
|
|
56
|
+
const body = {};
|
|
57
|
+
for (const key of ['path', 'name', 'integrationBranch', 'workspacePaths'])
|
|
58
|
+
if (args[key] !== undefined)
|
|
59
|
+
body[key] = args[key];
|
|
60
|
+
if (!Object.keys(body).length)
|
|
61
|
+
throw new Error('repository_update requires at least one field to update.');
|
|
62
|
+
return (0, types_1.apiCall)(ctx, action === 'repository_add' ? 'POST' : 'PATCH', action === 'repository_add' ? base : `${base}/${encodeURIComponent(id)}`, body);
|
|
63
|
+
}
|
|
39
64
|
case 'list':
|
|
40
65
|
return ctx.registry.listProjects().map((p) => ({ ...serializeProject(p), available: !!ctx.registry.getContext(p.id) }));
|
|
41
66
|
case 'get': {
|
|
@@ -53,7 +78,7 @@ function projectsTools() {
|
|
|
53
78
|
throw new Error('resolve requires a "path".');
|
|
54
79
|
const selected = args.projectId;
|
|
55
80
|
const key = (0, project_repositories_1.repositoryPathKey)((0, project_repositories_1.canonicalRepositoryPath)(p));
|
|
56
|
-
const matches = ctx.registry.listProjects().filter(row => !selected || row.id === selected).flatMap(project => (0, project_repositories_1.getProjectRepositories)(project).filter(repository => (0, project_repositories_1.repositoryPathKey)((0, project_repositories_1.canonicalRepositoryPath)(
|
|
81
|
+
const matches = ctx.registry.listProjects().filter(row => !selected || row.id === selected).flatMap(project => (0, project_repositories_1.getProjectRepositories)(project).filter(repository => [repository.path, ...(repository.workspacePaths ?? (repository.workspacePath ? [repository.workspacePath] : []))].some(root => (0, project_repositories_1.repositoryPathKey)((0, project_repositories_1.canonicalRepositoryPath)(root)) === key))
|
|
57
82
|
.map(repository => ({ project, repository })));
|
|
58
83
|
if (matches.length > 1)
|
|
59
84
|
return { resolved: false, ambiguous: true, matches: matches.map(({ project, repository }) => ({ projectId: project.id, projectName: project.name, repositoryId: repository.id, repositoryName: repository.name })) };
|
|
@@ -49,7 +49,7 @@ function railsTools() {
|
|
|
49
49
|
'When launched from the in-app agent chat without an explicit aiEngine, the engine defaults to your conversation\'s provider. On that same provider, an omitted model inherits the conversation model; an explicit model wins (pass aiEngine to override; launch_all always uses each rail\'s stored engine). ' +
|
|
50
50
|
'For Implement, the selected launch provider applies to ALL runtime roles (architect, developer, reviewer); an explicit launch model/effort also applies to each role. Resuming an existing execution keeps its original frozen role configuration; start a new launch to change providers. ' +
|
|
51
51
|
'User-facing naming: call the free-form autonomous mode "Freestyle"; use "freestyle" as the canonical API enum value for that same capability. ' +
|
|
52
|
-
'
|
|
52
|
+
'Always honor an explicit workflow selection, including Implement for addenda or delivery changes. Without a preference, for small OpenSpec-governed work, recommend "SDD Quick (OpenSpec)" and launch with mode "loop" plus loopId "factory:sdd-quick-openspec"; keep Freestyle for ticket-local implementation-only work. ' +
|
|
53
53
|
'NAMING: railIndex is the 0-BASED internal identity; the dashboard shows rails 1-based ("Rail N" = railIndex N-1). When talking to the user, ALWAYS say "Rail <railIndex + 1>" (or the rail\'s custom name) — results include railLabel with the correct user-facing label.',
|
|
54
54
|
hintTier: 'read',
|
|
55
55
|
tier: (a) => {
|
|
@@ -68,6 +68,7 @@ function railsTools() {
|
|
|
68
68
|
.describe('Operation to perform'),
|
|
69
69
|
projectId: zod_1.z.string().optional().describe('Project id (defaults to the active project)'),
|
|
70
70
|
repositoryIds: zod_1.z.array(zod_1.z.string().min(1)).min(1).max(50).optional().describe('launch only: explicit affected repository memberships for the coordinated run; must include every repository required by the assigned specs. Omission uses the assigned specs\' repository selections.'),
|
|
71
|
+
workspaceSelection: zod_1.z.record(zod_1.z.string(), zod_1.z.array(zod_1.z.string().min(1)).min(1)).optional().describe('launch only: map selected repository IDs to registered code workspace paths; discover workspacePaths through project context. Omission verifies all configured code workspaces. Never use the parent project path unless it is itself a registered code workspace.'),
|
|
71
72
|
repositoryId: zod_1.z.string().min(1).optional().describe('review_packet only: member of a grouped delivery to inspect; omission returns the public parent with grouped outcomes.'),
|
|
72
73
|
railIndex: zod_1.z
|
|
73
74
|
.number()
|
|
@@ -129,11 +130,11 @@ function railsTools() {
|
|
|
129
130
|
revisionOfDeliveryId: zod_1.z
|
|
130
131
|
.string()
|
|
131
132
|
.optional()
|
|
132
|
-
.describe('Revise a delivery that is ALREADY awaiting the user\'s decision (launch). Continues with Quick SDD (the Revision loop is retired): pass its prDeliveryId (from rails.prDeliveries) together with revisionNote. Use it whenever the user asks for a change to work you already delivered — never publish/discard/merge first. The rail must still carry exactly that delivery\'s specs.'),
|
|
133
|
+
.describe('Revise a delivery that is ALREADY awaiting the user\'s decision (launch). Continues with the explicitly selected loop, defaulting to Quick SDD (the Revision loop is retired): pass its prDeliveryId (from rails.prDeliveries) together with revisionNote. Use it whenever the user asks for a change to work you already delivered — never publish/discard/merge first. The rail must still carry exactly that delivery\'s specs.'),
|
|
133
134
|
revisionNote: zod_1.z
|
|
134
135
|
.string()
|
|
135
136
|
.optional()
|
|
136
|
-
.describe('What to change, in the user\'s own words (required with revisionOfDeliveryId). It is injected into every
|
|
137
|
+
.describe('What to change, in the user\'s own words (required with revisionOfDeliveryId). It is injected into every selected workflow phase and shown on the updated review packet as "what you asked to change".'),
|
|
137
138
|
followUp: zod_1.z
|
|
138
139
|
.object({
|
|
139
140
|
comments: zod_1.z.array(zod_1.z.object({
|
|
@@ -257,6 +258,8 @@ function railsTools() {
|
|
|
257
258
|
body.targetPrNumber = args.targetPrNumber;
|
|
258
259
|
if (args.repositoryIds !== undefined)
|
|
259
260
|
body.repositoryIds = args.repositoryIds;
|
|
261
|
+
if (args.workspaceSelection !== undefined)
|
|
262
|
+
body.workspaceSelection = args.workspaceSelection;
|
|
260
263
|
if (args.baseBranch !== undefined)
|
|
261
264
|
body.baseBranch = args.baseBranch;
|
|
262
265
|
// Revision of a delivery already awaiting the user's decision: the
|
|
@@ -88,6 +88,8 @@ function setupTools() {
|
|
|
88
88
|
.array(zod_1.z.string())
|
|
89
89
|
.optional()
|
|
90
90
|
.describe('add_project: providers to install (first = primary/default); de-duped server-side'),
|
|
91
|
+
workspacePaths: zod_1.z.array(zod_1.z.string().min(1)).min(1).max(50).optional().describe('add_project: code workspace directories inside the primary project root'),
|
|
92
|
+
repositories: zod_1.z.array(zod_1.z.object({ path: zod_1.z.string().min(1), name: zod_1.z.string().optional(), workspacePaths: zod_1.z.array(zod_1.z.string().min(1)).min(1).max(50).optional(), integrationBranch: zod_1.z.string().nullable().optional() })).max(50).optional().describe('add_project: additional repository memberships with their code workspaces'),
|
|
91
93
|
// install_config
|
|
92
94
|
config: zod_1.z
|
|
93
95
|
.record(zod_1.z.unknown())
|
|
@@ -115,6 +117,10 @@ function setupTools() {
|
|
|
115
117
|
body.provider = args.provider;
|
|
116
118
|
if (Array.isArray(args.providers))
|
|
117
119
|
body.providers = args.providers;
|
|
120
|
+
if (args.workspacePaths !== undefined)
|
|
121
|
+
body.workspacePaths = args.workspacePaths;
|
|
122
|
+
if (args.repositories !== undefined)
|
|
123
|
+
body.repositories = args.repositories;
|
|
118
124
|
return (0, types_1.apiCall)(ctx, 'POST', '/projects', body);
|
|
119
125
|
}
|
|
120
126
|
// ─── Per-project ────────────────────────────────────────────────────
|
|
@@ -137,6 +143,10 @@ function setupTools() {
|
|
|
137
143
|
const body = {};
|
|
138
144
|
if (Array.isArray(args.providers))
|
|
139
145
|
body.providers = args.providers;
|
|
146
|
+
if (args.workspacePaths !== undefined)
|
|
147
|
+
body.workspacePaths = args.workspacePaths;
|
|
148
|
+
if (args.repositories !== undefined)
|
|
149
|
+
body.repositories = args.repositories;
|
|
140
150
|
else if (typeof args.provider === 'string')
|
|
141
151
|
body.provider = args.provider;
|
|
142
152
|
else
|
|
@@ -313,7 +313,7 @@ function specsTools() {
|
|
|
313
313
|
});
|
|
314
314
|
return {
|
|
315
315
|
...r,
|
|
316
|
-
hint: 'The addendum is open:
|
|
316
|
+
hint: 'The addendum is open: respect the user\'s explicit loop selection, including factory:implement; otherwise recommend factory:sdd-quick-openspec. Rail launches continue a same-spec pending delivery, and brief every AI step with the full delta and a fresh OpenSpec target. Do not use Revision or edit openspecChangeName. Do not edit the spec description to repeat it.',
|
|
317
317
|
};
|
|
318
318
|
}
|
|
319
319
|
case 'update_addendum': {
|
|
@@ -76,7 +76,10 @@ exports.PRICING = {
|
|
|
76
76
|
// Opus 5.5: https://platform.claude.com/docs/en/models/opus-5-5/overview
|
|
77
77
|
'claude:opus': { inputPer1M: 4.00, outputPer1M: 20.00, cacheReadPer1M: 0.20, cacheWritePer1M: 5.00, inputIncludesCacheReads: false, lastReviewedAt: '2026-09-24' },
|
|
78
78
|
'claude:claude-opus-5-5': { inputPer1M: 4.00, outputPer1M: 20.00, cacheReadPer1M: 0.20, cacheWritePer1M: 5.00, inputIncludesCacheReads: false, lastReviewedAt: '2026-09-24' },
|
|
79
|
-
'claude:sonnet': { inputPer1M: 3.00, outputPer1M: 15.00, cacheReadPer1M: 0.30, cacheWritePer1M: 3.75, inputIncludesCacheReads: false, lastReviewedAt: '2026-07-02' },
|
|
79
|
+
'claude:claude-sonnet-5': { inputPer1M: 3.00, outputPer1M: 15.00, cacheReadPer1M: 0.30, cacheWritePer1M: 3.75, inputIncludesCacheReads: false, lastReviewedAt: '2026-07-02' },
|
|
80
|
+
// Sonnet 5.5: https://platform.claude.com/docs/en/models/sonnet-5-5/overview
|
|
81
|
+
'claude:sonnet': { inputPer1M: 2.00, outputPer1M: 10.00, cacheReadPer1M: 0.20, cacheWritePer1M: 2.50, inputIncludesCacheReads: false, lastReviewedAt: '2026-09-30' },
|
|
82
|
+
'claude:claude-sonnet-5-5': { inputPer1M: 2.00, outputPer1M: 10.00, cacheReadPer1M: 0.20, cacheWritePer1M: 2.50, inputIncludesCacheReads: false, lastReviewedAt: '2026-09-30' },
|
|
80
83
|
'claude:haiku': { inputPer1M: 1.00, outputPer1M: 5.00, cacheReadPer1M: 0.10, cacheWritePer1M: 1.25, inputIncludesCacheReads: false, lastReviewedAt: '2026-07-02' },
|
|
81
84
|
'claude:fable': { inputPer1M: 10.00, outputPer1M: 50.00, cacheReadPer1M: 1.00, cacheWritePer1M: 12.50, inputIncludesCacheReads: false, lastReviewedAt: '2026-07-02' },
|
|
82
85
|
};
|
|
@@ -96,6 +99,8 @@ function resolvePriceEntry(providerId, model) {
|
|
|
96
99
|
// Keep the existing estimates for older concrete Opus generations.
|
|
97
100
|
if (/^claude-opus-(?:4(?:-|$)|5$)/.test(model))
|
|
98
101
|
return exports.PRICING['claude:claude-opus-5'];
|
|
102
|
+
if (/^claude-sonnet-(?:4(?:-|$)|5$)/.test(model))
|
|
103
|
+
return exports.PRICING['claude:claude-sonnet-5'];
|
|
99
104
|
const family = CLAUDE_MODEL_FAMILY.exec(model);
|
|
100
105
|
if (family)
|
|
101
106
|
return exports.PRICING[`claude:${family[1]}`];
|