specrails-desktop 2.58.0 → 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
|
@@ -21,11 +21,6 @@ SpecsBoard (left) Rails (right)
|
|
|
21
21
|
|
|
22
22
|
A **rail** is an execution lane. Drag a spec card from the SpecsBoard onto a rail and press **▶ Play** to launch the pipeline. Rails let you organise and queue work into named lanes.
|
|
23
23
|
|
|
24
|
-
Rails default to the project's primary provider. If several are installed, a
|
|
25
|
-
per-rail engine selector can launch on **Claude**, **Codex**, **Gemini**, or
|
|
26
|
-
**Kimi** — see [Using Kimi](kimi.md). Implement runs on every
|
|
27
|
-
registered provider; Freestyle is offered by Claude and Kimi.
|
|
28
|
-
|
|
29
24
|
> **One job at a time per project.** Each project has a single queue, so within a project only one rail job runs at a time; the rest queue behind it. Real parallelism is **across projects** — open two projects and their rails run independently. See [Running multiple rails](#running-multiple-rails).
|
|
30
25
|
|
|
31
26
|
## Rails
|
|
@@ -36,8 +31,6 @@ Each rail has a header with:
|
|
|
36
31
|
- **Spec list** — the IDs of the specs assigned to this rail. Drag in more, drag out to detach. You can also use the **Move to rail** popover from a spec card; it shows a status dot per rail so you don't push work onto a busy lane.
|
|
37
32
|
- **Loop picker** — the **Loop** this rail runs: a built-in (`Implement`, `SDD Quick (OpenSpec)`, or `Freestyle`) or one of your published custom loops. See [Loops](#loops).
|
|
38
33
|
- **Profile picker** — which agent profile this rail uses. This only appears once the project has **at least one** profile (create them on the Agents page). When present, `No profile` runs the rail in legacy mode.
|
|
39
|
-
- **Engine selector** — pick which installed provider (Claude, Codex, Gemini,
|
|
40
|
-
or Kimi) runs this rail. It renders only for multi-provider projects.
|
|
41
34
|
- **Play / Stop button** — start or cancel.
|
|
42
35
|
|
|
43
36
|
### Loops
|
|
@@ -67,11 +60,6 @@ Beyond the built-ins, the **Loops** section (left sidebar, above the project lis
|
|
|
67
60
|
|
|
68
61
|
Each run is bounded by **max iterations**, a wall-clock **timeout**, and an optional **cost cap** (USD, checked between steps). The builder also has live validation, a dry-run preview (resolve every step's exact text without spawning), import/export to JSON, and copy/paste of steps across loops. **Publish** makes a loop selectable on any rail. Built-ins are editable loops too: edit one in place and publish it to change that built-in for every rail, agent-chat launch and Companion launch that uses it (rails keep the last published version while your edit is a Draft); **Restore original** resets it and **Duplicate** copies it into a separate loop. Built-ins cannot be deleted.
|
|
69
62
|
|
|
70
|
-
On the legacy traversal, Kimi runs built-in and custom loops whose graph contains AI/shell steps but no
|
|
71
|
-
**Loop Decider**. A Decider's constrained `continue`/`stop` verdict is a
|
|
72
|
-
pure-output action; Kimi 0.27 `-p` cannot enforce the required no-tools
|
|
73
|
-
boundary, so Kimi + Decider is rejected before the loop starts.
|
|
74
|
-
|
|
75
63
|
> Loop runs stream live in the Jobs view, grouped by scoped attempts. Core runs also expose their nested graph. Project role settings apply unless a deliberate launch override selects an engine for the run. Legacy profile routing and orchestrator settings remain available for older workflows.
|
|
76
64
|
|
|
77
65
|
### Pipeline phases
|
|
@@ -96,9 +84,6 @@ continue through the ordinary verification path.
|
|
|
96
84
|
Architect ──► Developer ──► Reviewer ──► Ship
|
|
97
85
|
```
|
|
98
86
|
|
|
99
|
-
Each phase is a specialised role invoked by the rail's engine (Claude, Codex,
|
|
100
|
-
Gemini, or Kimi) in an isolated project worktree:
|
|
101
|
-
|
|
102
87
|
| Phase | Agent | What it does |
|
|
103
88
|
|-------|-------|--------------|
|
|
104
89
|
| Architect | `sr-architect` | Plans the implementation |
|
|
@@ -119,17 +104,8 @@ autonomously with native tools.
|
|
|
119
104
|
|
|
120
105
|
- **One job per spec.** If the rail has three specs, `Freestyle` launches three independent jobs.
|
|
121
106
|
- **Variable cost.** Because the run is open-ended, pressing Play opens a confirmation dialog before anything spawns.
|
|
122
|
-
- **
|
|
123
|
-
catalog. Kimi offers `k3`, `kimi-for-coding`, and
|
|
124
|
-
`kimi-for-coding-highspeed`; low/high/max effort appears only for K3.
|
|
125
|
-
- **Capability-gated.** Claude and Kimi advertise Freestyle. Codex and Gemini
|
|
107
|
+
- **Capability-gated.** Claude advertise Freestyle. Codex
|
|
126
108
|
do not. Agent profiles do not apply to Freestyle rails.
|
|
127
|
-
- **Transport-aware interaction.** Claude's persistent-stdin Freestyle run is
|
|
128
|
-
a live session and waits for **Finalize**. Kimi Freestyle is agentic too,
|
|
129
|
-
but `kimi -p` has no persistent stdin transport; it follows the owned
|
|
130
|
-
one-process lifecycle and later conversations resume through a new process
|
|
131
|
-
only after Kimi has emitted a session hint. Can be disabled server-side via
|
|
132
|
-
`SPECRAILS_INTERACTIVE_JOBS=false`.
|
|
133
109
|
|
|
134
110
|
You can customise the Freestyle pre-prompt per project on the [Settings page](customizing.md#freestyle-pre-prompt).
|
|
135
111
|
|
|
@@ -166,20 +142,10 @@ Two panels sit above the streaming log:
|
|
|
166
142
|
|
|
167
143
|
Below: the full streaming log with auto-scroll, search, and copy.
|
|
168
144
|
|
|
169
|
-
**Talk to the running job.** Every Claude job (and every Claude AI step in a loop run) is an interactive session by default: a chat composer sits at the bottom of the Job Detail page — and of the job modal in mission mode — so you can ask the running agent a question or steer it mid-run. Messages sent while the model is streaming queue up and run next; the job keeps following its plan. The composer shows a live `N turns · $X` line summed from each completed turn's real usage (still no estimates). Most jobs finish on their own the moment a turn completes with nothing queued — a subtle **Wrap up now** ends them early; Freestyle jobs instead wait for your explicit **Finalize**. During a loop run your messages reach the currently running AI step (between steps the composer shows a short waiting state, and **Settle this step** advances the loop). Codex, Gemini, and Kimi rail processes do not expose Claude's persistent-stdin transport; Kimi chat surfaces resume a recorded session by spawning a new `kimi -p` process. Server kill switch: `SPECRAILS_INTERACTIVE_JOBS=false`.
|
|
170
|
-
|
|
171
145
|
### Cancelling a job
|
|
172
146
|
|
|
173
147
|
Click **Stop** on the rail header. The app sends `SIGTERM` to the subprocess, waits **5 s**, then `SIGKILL`.
|
|
174
148
|
|
|
175
|
-
### If a rail won't launch
|
|
176
|
-
|
|
177
|
-
If you pick an engine whose CLI isn't installed on your machine, the launch
|
|
178
|
-
fails fast instead of starting a broken job. Install the missing provider CLI
|
|
179
|
-
— see [Using Codex](codex.md), [Using Gemini](gemini.md), or
|
|
180
|
-
[Using Kimi](kimi.md) — then launch again. Kimi requires the external
|
|
181
|
-
authenticated `kimi` 0.27+ executable; Desktop never starts a server for it.
|
|
182
|
-
|
|
183
149
|
### Diagnostic export
|
|
184
150
|
|
|
185
151
|
Visible only when [telemetry](customizing.md#telemetry) was enabled for the job. Click **Export diagnostic** in the Job Detail header to download a ZIP containing:
|
|
@@ -219,9 +185,6 @@ Pick the profile from the **rail header's profile dropdown**. It's preselected t
|
|
|
219
185
|
|
|
220
186
|
The **`No profile`** option always exists — use it to run a rail exactly as it did pre-4.1.0. (The dropdown itself only appears once the project has at least one profile.)
|
|
221
187
|
|
|
222
|
-
> **Profiles apply to providers that advertise role/profile execution.** Claude
|
|
223
|
-
> and Kimi support them. Codex and Gemini currently run in legacy mode.
|
|
224
|
-
|
|
225
188
|
### Custom agents (Agent Studio)
|
|
226
189
|
|
|
227
190
|
From the Agents Catalog tab, the toolbar offers three creation entry points:
|
|
@@ -256,8 +219,6 @@ Per-project bundled integrations. Click **Integrations** in the project's right
|
|
|
256
219
|
|
|
257
220
|
Each plugin tile has:
|
|
258
221
|
|
|
259
|
-
- **AI provider** — for multi-provider projects, selects the independent
|
|
260
|
-
Claude, Codex, or Kimi installation to inspect or change.
|
|
261
222
|
- **Status** — `not installed`, `installed`, `orphan` (state file mentions it but `.mcp.json` doesn't), or `degraded` (verify failed).
|
|
262
223
|
- **Preview install** — shows which `mcpServers` entries and agent fragments will land where, so you can sanity-check before clicking.
|
|
263
224
|
- **Install** — applies the changes. Progress streams over the WebSocket (`plugin.install_progress` event).
|
|
@@ -281,11 +242,6 @@ Degraded plugins are **non-blocking** — the rail spawns anyway, but a `plugin.
|
|
|
281
242
|
The app never wholesale rewrites these files:
|
|
282
243
|
|
|
283
244
|
- `<project>/.mcp.json` — surgical merge per plugin.
|
|
284
|
-
- `<project>/.kimi-code/mcp.json` — Kimi-native surgical MCP merge.
|
|
285
|
-
- `<project>/CLAUDE.md`, `<project>/AGENTS.md`,
|
|
286
|
-
`<project>/GEMINI.md`, or `<project>/.kimi-code/AGENTS.md` —
|
|
287
|
-
provider-native managed instruction blocks. Provider-relative paths remain
|
|
288
|
-
independent unless two adapters explicitly declare the same path.
|
|
289
245
|
- `<project>/.specrails/plugins/state.json` — install registry.
|
|
290
246
|
- `<project>/.specrails/plugins/snapshots/<jobId>.json` — per-job snapshots.
|
|
291
247
|
- `<project>/.claude/agents/custom-<plugin>.md` — optional fragment per plugin.
|
|
@@ -315,5 +271,4 @@ If something looks wrong:
|
|
|
315
271
|
- [Tracking cost](tracking-cost.md) — see what each rail run is costing you.
|
|
316
272
|
- [Customising the app](customizing.md) — daily budget, per-job alerts, telemetry.
|
|
317
273
|
- [Using Codex](codex.md) — run rails on the Codex CLI.
|
|
318
|
-
- [Using Gemini](gemini.md) — run rails on the Gemini CLI.
|
|
319
274
|
- [Agent profile internals](internals/profiles.md) — for power users.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Local subscription usage
|
|
2
|
+
|
|
3
|
+
The left sidebar's **Usage** section shows the currently signed-in Claude and Codex account allowance across all activity, including use outside Specrails. Each provider window has its own consumed percentage and reset time; this is separate from job tokens, billed costs and project budgets.
|
|
4
|
+
|
|
5
|
+
The sidebar displays static provider cards with separate labeled quota bars. There is no floating usage panel or clickable provider card. Full reset times and data details are available in Subscription usage in global app settings. The view is always detailed, without view selectors or provider-management buttons. The collapsed sidebar shows a usage status icon. Unknown measurements are unavailable, never assumed to be zero.
|
|
6
|
+
|
|
7
|
+
If neither CLI is detected, the section explains installing Claude or Codex and signing in with that CLI. It does not run usage queries for missing tools. After installation or changing login, press Refresh. An API-key login may have no subscription allowance. An incompatible Codex CLI must be updated rather than queried through a paid prompt.
|
|
8
|
+
|
|
9
|
+
Updates are demand-driven while the app and a usage surface are visible. Manual refresh has a 30-second floor and provider failures can impose a longer cooldown. Data becomes stale after five minutes or a passed reset; refresh confirms the new allowance. A transient failure can retain figures from the same account with a stale label. Sign-out/auth failures clear cached figures.
|
|
10
|
+
|
|
11
|
+
Credentials remain local to the server and are never sent to the frontend. The feature does not manage accounts, change provider selection, stop jobs or store usage history. Claude OAuth/Keychain availability depends on the installed CLI and OS access policy; Codex uses read-only app-server methods. Unsupported login or platform combinations show a descriptive state. Only the current host login is supported; remote/WSL account usage is not inferred.
|
|
12
|
+
|
|
13
|
+
The Usage section starts collapsed. Its header toggles the provider cards with an accessible expanded state and chevron; the separate refresh button does not toggle the section.
|
|
14
|
+
|
|
15
|
+
Opening the Usage section requests a refresh each time, retaining cached measurements while collection runs and respecting service cooldowns. Closing it does not request a refresh.
|
|
16
|
+
|
|
17
|
+
Final placement: Usage is in the shared footer status bar in board and mission modes, removed from the left sidebar. Hover, focus or click opens the provider cards above the footer; opening requests refresh. Pointer transfer to the menu stays open, leaving dismisses after a short delay, and Escape/outside interaction closes it. Mission mode uses the minimal status bar without spend polling. This supersedes prior sidebar placement.
|
|
18
|
+
|
|
19
|
+
Footer summaries show one quota per provider: account weekly when reported, otherwise the first account window, otherwise the first reported window. Values are never aggregated. The hover menu shows every individual quota and bar. There is no presentation selector or mode label.
|
package/docs/terminal.md
CHANGED
|
@@ -32,7 +32,6 @@ You don't have to. You can keep using iTerm/Windows Terminal/Alacritty/whatever
|
|
|
32
32
|
|
|
33
33
|
The panel header carries three one-click shortcuts:
|
|
34
34
|
|
|
35
|
-
- **Open AI CLI** (✨ Sparkles) — spins up a fresh session and types the launch command for you. On a single-provider project it launches the project's provider CLI directly (`claude`, `codex`, `gemini`, or `kimi`); on a multi-provider project it opens a small picker so you choose which CLI to start. Kimi launches the CLI itself, not `kimi server`.
|
|
36
35
|
- **Open in browser** (🌐 Globe) — opens the URL from the `browserShortcutUrl` setting (default `https://specrails.dev`). Right-click it to jump to the setting and change the URL.
|
|
37
36
|
- **Paste quick script** (`</>`) — writes the snippet from the `quickScript` setting (which defaults to a personalised `echo "Wake up, <username> …"` reminder, editable in Settings) into the active session. Right-click it to edit the snippet in Settings. Disabled when there's no active terminal or the snippet is empty.
|
|
38
37
|
|
|
@@ -141,4 +140,3 @@ For deeper inspection of how the panel resolves PATH at startup (relevant for Vo
|
|
|
141
140
|
|
|
142
141
|
- [Customising the app](customizing.md) — terminal settings, themes.
|
|
143
142
|
- [Getting started](getting-started.md) — registering a project.
|
|
144
|
-
- [Codex](codex.md) / [Gemini](gemini.md) — setting up the CLI behind the **Open AI CLI** button on a Codex or Gemini project.
|
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.
|
|
@@ -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)
|