specrails-desktop 2.7.0 → 2.9.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 +23 -19
- package/client/dist/assets/{ActivityFeedPage-LKqd18-G.js → ActivityFeedPage-DNqnf1fZ.js} +1 -1
- package/client/dist/assets/{AgentsPage-Cb-b-6Ot.js → AgentsPage-vmNIEbGM.js} +1 -1
- package/client/dist/assets/{AnalyticsPage-HVxQQ1wy.js → AnalyticsPage-CdfN0ofZ.js} +1 -1
- package/client/dist/assets/{BarChart-BOyHB0dw.js → BarChart-CIkopHjl.js} +1 -1
- package/client/dist/assets/{CodePage-DnOnwKGB.js → CodePage-DDRNU5FN.js} +1 -1
- package/client/dist/assets/{DesktopAnalyticsPage-D2auU39x.js → DesktopAnalyticsPage-Cl3sKKSG.js} +1 -1
- package/client/dist/assets/{DocsDialog-CTuDX3GK.js → DocsDialog-BGrBOfUr.js} +2 -2
- package/client/dist/assets/{DocsPage-DRyMmu0Z.js → DocsPage-CY-2SSzw.js} +2 -2
- package/client/dist/assets/{ExportDropdown-DO-GGiMh.js → ExportDropdown-BRHcvP0r.js} +1 -1
- package/client/dist/assets/{IntegrationsPage-BhbO4jFT.js → IntegrationsPage-nKdLB4Ub.js} +1 -1
- package/client/dist/assets/{JobDetailPage-DJooEg1s.js → JobDetailPage-Bf0A6WWQ.js} +1 -1
- package/client/dist/assets/{JobsPage-BbaC-YOg.js → JobsPage-Vg4nXPdL.js} +1 -1
- package/client/dist/assets/{dist-js-CiIVMsx3.js → dist-js-0i_klubI.js} +1 -1
- package/client/dist/assets/{dist-js-Xc2lRKp2.js → dist-js-CUs5GjwA.js} +1 -1
- package/client/dist/assets/{index-DK214dak.js → index-BXoHFtfG.js} +8 -8
- package/client/dist/assets/index-D6BaYRRU.css +2 -0
- package/client/dist/assets/{integrations-2C7MkGT0.js → integrations-7YyTBuU9.js} +1 -1
- package/client/dist/assets/{integrations-CX4p_bij.js → integrations-B9CEpNF0.js} +1 -1
- package/client/dist/assets/{integrations-C2jQtv-s.js → integrations-BlvAdewo.js} +1 -1
- package/client/dist/assets/{integrations-eQPHAYsE.js → integrations-Bw8UM9Xd.js} +1 -1
- package/client/dist/assets/{integrations-BDC670cg.js → integrations-C5SxNKnG.js} +1 -1
- package/client/dist/assets/{integrations-BqUmRUef.js → integrations-CJQKMmdW.js} +1 -1
- package/client/dist/assets/{integrations-CB98NeH5.js → integrations-DWz1eU_K.js} +1 -1
- package/client/dist/assets/{integrations-_SuVeQIG.js → integrations-DiPR8Fzp.js} +1 -1
- package/client/dist/assets/{lib-Bo5s6xpe.js → lib-D6M_MvoC.js} +1 -1
- package/client/dist/assets/setup-B6egeeTM.js +1 -0
- package/client/dist/assets/setup-BHroXlke.js +1 -0
- package/client/dist/assets/setup-BIXsWUp1.js +1 -0
- package/client/dist/assets/setup-BJRdg1iE.js +1 -0
- package/client/dist/assets/setup-C0rVGnCy.js +1 -0
- package/client/dist/assets/setup-Cpu17hJv.js +1 -0
- package/client/dist/assets/setup-D-1r0uSx.js +1 -0
- package/client/dist/assets/setup-Dn2-veYO.js +1 -0
- package/client/dist/assets/{useProjectCache-DVNypkmR.js → useProjectCache-BeyBSNpD.js} +1 -1
- package/client/dist/index.html +4 -4
- package/docs/README.md +5 -2
- package/docs/agy-cli-provider-study.md +78 -0
- package/docs/cli.md +23 -4
- package/docs/codex.md +116 -58
- package/docs/creating-specs.md +19 -5
- package/docs/customizing.md +27 -6
- package/docs/gemini.md +225 -73
- package/docs/getting-started.md +18 -9
- package/docs/guide/de/agents/1-meet-the-agents.md +38 -0
- package/docs/guide/de/agents/2-profiles-and-the-balanced-default.md +45 -0
- package/docs/guide/de/agents/3-customizing-models-per-agent.md +60 -0
- package/docs/guide/de/agents/4-custom-agents-catalog.md +43 -0
- package/docs/guide/de/getting-started/1-what-is-specrails.md +49 -0
- package/docs/guide/de/getting-started/2-installing-and-first-run.md +42 -0
- package/docs/guide/de/getting-started/3-adding-your-first-project.md +58 -0
- package/docs/guide/de/getting-started/4-the-dashboard-tour.md +53 -0
- package/docs/guide/de/insights/1-analytics-and-cost-tracking.md +78 -0
- package/docs/guide/de/insights/2-the-integrated-terminal.md +46 -0
- package/docs/guide/de/insights/3-code-explorer.md +50 -0
- package/docs/guide/de/integrations/1-ai-providers.md +64 -0
- package/docs/guide/de/integrations/2-plugins.md +44 -0
- package/docs/guide/de/integrations/3-jira-integration.md +71 -0
- package/docs/guide/de/integrations/4-mobile-companion.md +38 -0
- package/docs/guide/de/pipeline/1-rails-and-jobs.md +94 -0
- package/docs/guide/de/pipeline/2-the-job-detail-view.md +90 -0
- package/docs/guide/de/pipeline/3-batch-implement-and-multi-feature.md +78 -0
- package/docs/guide/de/pipeline/4-picking-an-engine-per-rail.md +60 -0
- package/docs/guide/de/settings/1-themes.md +37 -0
- package/docs/guide/de/settings/2-language.md +39 -0
- package/docs/guide/de/settings/3-pipeline-telemetry-and-diagnostics.md +46 -0
- package/docs/guide/de/settings/4-where-your-data-lives.md +48 -0
- package/docs/guide/de/specs/1-specs-and-the-backlog.md +52 -0
- package/docs/guide/de/specs/2-add-spec-quick-mode.md +45 -0
- package/docs/guide/de/specs/3-add-spec-explore-mode.md +68 -0
- package/docs/guide/de/specs/4-drafts-and-contract-layer.md +81 -0
- package/docs/guide/en/agents/1-meet-the-agents.md +38 -0
- package/docs/guide/en/agents/2-profiles-and-the-balanced-default.md +45 -0
- package/docs/guide/en/agents/3-customizing-models-per-agent.md +60 -0
- package/docs/guide/en/agents/4-custom-agents-catalog.md +43 -0
- package/docs/guide/en/getting-started/1-what-is-specrails.md +49 -0
- package/docs/guide/en/getting-started/2-installing-and-first-run.md +42 -0
- package/docs/guide/en/getting-started/3-adding-your-first-project.md +58 -0
- package/docs/guide/en/getting-started/4-the-dashboard-tour.md +53 -0
- package/docs/guide/en/insights/1-analytics-and-cost-tracking.md +78 -0
- package/docs/guide/en/insights/2-the-integrated-terminal.md +46 -0
- package/docs/guide/en/insights/3-code-explorer.md +50 -0
- package/docs/guide/en/integrations/1-ai-providers.md +64 -0
- package/docs/guide/en/integrations/2-plugins.md +44 -0
- package/docs/guide/en/integrations/3-jira-integration.md +71 -0
- package/docs/guide/en/integrations/4-mobile-companion.md +38 -0
- package/docs/guide/en/pipeline/1-rails-and-jobs.md +94 -0
- package/docs/guide/en/pipeline/2-the-job-detail-view.md +90 -0
- package/docs/guide/en/pipeline/3-batch-implement-and-multi-feature.md +78 -0
- package/docs/guide/en/pipeline/4-picking-an-engine-per-rail.md +60 -0
- package/docs/guide/en/settings/1-themes.md +37 -0
- package/docs/guide/en/settings/2-language.md +39 -0
- package/docs/guide/en/settings/3-pipeline-telemetry-and-diagnostics.md +46 -0
- package/docs/guide/en/settings/4-where-your-data-lives.md +48 -0
- package/docs/guide/en/specs/1-specs-and-the-backlog.md +52 -0
- package/docs/guide/en/specs/2-add-spec-quick-mode.md +45 -0
- package/docs/guide/en/specs/3-add-spec-explore-mode.md +68 -0
- package/docs/guide/en/specs/4-drafts-and-contract-layer.md +81 -0
- package/docs/guide/es/agents/1-meet-the-agents.md +38 -0
- package/docs/guide/es/agents/2-profiles-and-the-balanced-default.md +45 -0
- package/docs/guide/es/agents/3-customizing-models-per-agent.md +60 -0
- package/docs/guide/es/agents/4-custom-agents-catalog.md +43 -0
- package/docs/guide/es/getting-started/1-what-is-specrails.md +49 -0
- package/docs/guide/es/getting-started/2-installing-and-first-run.md +42 -0
- package/docs/guide/es/getting-started/3-adding-your-first-project.md +58 -0
- package/docs/guide/es/getting-started/4-the-dashboard-tour.md +53 -0
- package/docs/guide/es/insights/1-analytics-and-cost-tracking.md +78 -0
- package/docs/guide/es/insights/2-the-integrated-terminal.md +46 -0
- package/docs/guide/es/insights/3-code-explorer.md +50 -0
- package/docs/guide/es/integrations/1-ai-providers.md +64 -0
- package/docs/guide/es/integrations/2-plugins.md +44 -0
- package/docs/guide/es/integrations/3-jira-integration.md +71 -0
- package/docs/guide/es/integrations/4-mobile-companion.md +38 -0
- package/docs/guide/es/pipeline/1-rails-and-jobs.md +94 -0
- package/docs/guide/es/pipeline/2-the-job-detail-view.md +90 -0
- package/docs/guide/es/pipeline/3-batch-implement-and-multi-feature.md +78 -0
- package/docs/guide/es/pipeline/4-picking-an-engine-per-rail.md +60 -0
- package/docs/guide/es/settings/1-themes.md +37 -0
- package/docs/guide/es/settings/2-language.md +39 -0
- package/docs/guide/es/settings/3-pipeline-telemetry-and-diagnostics.md +46 -0
- package/docs/guide/es/settings/4-where-your-data-lives.md +48 -0
- package/docs/guide/es/specs/1-specs-and-the-backlog.md +52 -0
- package/docs/guide/es/specs/2-add-spec-quick-mode.md +45 -0
- package/docs/guide/es/specs/3-add-spec-explore-mode.md +68 -0
- package/docs/guide/es/specs/4-drafts-and-contract-layer.md +81 -0
- package/docs/guide/fr/agents/1-meet-the-agents.md +38 -0
- package/docs/guide/fr/agents/2-profiles-and-the-balanced-default.md +45 -0
- package/docs/guide/fr/agents/3-customizing-models-per-agent.md +60 -0
- package/docs/guide/fr/agents/4-custom-agents-catalog.md +43 -0
- package/docs/guide/fr/getting-started/1-what-is-specrails.md +49 -0
- package/docs/guide/fr/getting-started/2-installing-and-first-run.md +42 -0
- package/docs/guide/fr/getting-started/3-adding-your-first-project.md +58 -0
- package/docs/guide/fr/getting-started/4-the-dashboard-tour.md +53 -0
- package/docs/guide/fr/insights/1-analytics-and-cost-tracking.md +78 -0
- package/docs/guide/fr/insights/2-the-integrated-terminal.md +46 -0
- package/docs/guide/fr/insights/3-code-explorer.md +50 -0
- package/docs/guide/fr/integrations/1-ai-providers.md +64 -0
- package/docs/guide/fr/integrations/2-plugins.md +44 -0
- package/docs/guide/fr/integrations/3-jira-integration.md +71 -0
- package/docs/guide/fr/integrations/4-mobile-companion.md +38 -0
- package/docs/guide/fr/pipeline/1-rails-and-jobs.md +94 -0
- package/docs/guide/fr/pipeline/2-the-job-detail-view.md +90 -0
- package/docs/guide/fr/pipeline/3-batch-implement-and-multi-feature.md +78 -0
- package/docs/guide/fr/pipeline/4-picking-an-engine-per-rail.md +60 -0
- package/docs/guide/fr/settings/1-themes.md +37 -0
- package/docs/guide/fr/settings/2-language.md +39 -0
- package/docs/guide/fr/settings/3-pipeline-telemetry-and-diagnostics.md +46 -0
- package/docs/guide/fr/settings/4-where-your-data-lives.md +48 -0
- package/docs/guide/fr/specs/1-specs-and-the-backlog.md +52 -0
- package/docs/guide/fr/specs/2-add-spec-quick-mode.md +45 -0
- package/docs/guide/fr/specs/3-add-spec-explore-mode.md +68 -0
- package/docs/guide/fr/specs/4-drafts-and-contract-layer.md +81 -0
- package/docs/guide/it/agents/1-meet-the-agents.md +38 -0
- package/docs/guide/it/agents/2-profiles-and-the-balanced-default.md +45 -0
- package/docs/guide/it/agents/3-customizing-models-per-agent.md +60 -0
- package/docs/guide/it/agents/4-custom-agents-catalog.md +43 -0
- package/docs/guide/it/getting-started/1-what-is-specrails.md +49 -0
- package/docs/guide/it/getting-started/2-installing-and-first-run.md +42 -0
- package/docs/guide/it/getting-started/3-adding-your-first-project.md +58 -0
- package/docs/guide/it/getting-started/4-the-dashboard-tour.md +53 -0
- package/docs/guide/it/insights/1-analytics-and-cost-tracking.md +78 -0
- package/docs/guide/it/insights/2-the-integrated-terminal.md +46 -0
- package/docs/guide/it/insights/3-code-explorer.md +50 -0
- package/docs/guide/it/integrations/1-ai-providers.md +64 -0
- package/docs/guide/it/integrations/2-plugins.md +44 -0
- package/docs/guide/it/integrations/3-jira-integration.md +71 -0
- package/docs/guide/it/integrations/4-mobile-companion.md +38 -0
- package/docs/guide/it/pipeline/1-rails-and-jobs.md +94 -0
- package/docs/guide/it/pipeline/2-the-job-detail-view.md +90 -0
- package/docs/guide/it/pipeline/3-batch-implement-and-multi-feature.md +78 -0
- package/docs/guide/it/pipeline/4-picking-an-engine-per-rail.md +60 -0
- package/docs/guide/it/settings/1-themes.md +37 -0
- package/docs/guide/it/settings/2-language.md +39 -0
- package/docs/guide/it/settings/3-pipeline-telemetry-and-diagnostics.md +46 -0
- package/docs/guide/it/settings/4-where-your-data-lives.md +48 -0
- package/docs/guide/it/specs/1-specs-and-the-backlog.md +52 -0
- package/docs/guide/it/specs/2-add-spec-quick-mode.md +45 -0
- package/docs/guide/it/specs/3-add-spec-explore-mode.md +68 -0
- package/docs/guide/it/specs/4-drafts-and-contract-layer.md +81 -0
- package/docs/guide/ja/agents/1-meet-the-agents.md +38 -0
- package/docs/guide/ja/agents/2-profiles-and-the-balanced-default.md +45 -0
- package/docs/guide/ja/agents/3-customizing-models-per-agent.md +60 -0
- package/docs/guide/ja/agents/4-custom-agents-catalog.md +43 -0
- package/docs/guide/ja/getting-started/1-what-is-specrails.md +49 -0
- package/docs/guide/ja/getting-started/2-installing-and-first-run.md +42 -0
- package/docs/guide/ja/getting-started/3-adding-your-first-project.md +58 -0
- package/docs/guide/ja/getting-started/4-the-dashboard-tour.md +53 -0
- package/docs/guide/ja/insights/1-analytics-and-cost-tracking.md +78 -0
- package/docs/guide/ja/insights/2-the-integrated-terminal.md +46 -0
- package/docs/guide/ja/insights/3-code-explorer.md +50 -0
- package/docs/guide/ja/integrations/1-ai-providers.md +64 -0
- package/docs/guide/ja/integrations/2-plugins.md +44 -0
- package/docs/guide/ja/integrations/3-jira-integration.md +71 -0
- package/docs/guide/ja/integrations/4-mobile-companion.md +38 -0
- package/docs/guide/ja/pipeline/1-rails-and-jobs.md +94 -0
- package/docs/guide/ja/pipeline/2-the-job-detail-view.md +90 -0
- package/docs/guide/ja/pipeline/3-batch-implement-and-multi-feature.md +78 -0
- package/docs/guide/ja/pipeline/4-picking-an-engine-per-rail.md +60 -0
- package/docs/guide/ja/settings/1-themes.md +37 -0
- package/docs/guide/ja/settings/2-language.md +39 -0
- package/docs/guide/ja/settings/3-pipeline-telemetry-and-diagnostics.md +46 -0
- package/docs/guide/ja/settings/4-where-your-data-lives.md +48 -0
- package/docs/guide/ja/specs/1-specs-and-the-backlog.md +52 -0
- package/docs/guide/ja/specs/2-add-spec-quick-mode.md +45 -0
- package/docs/guide/ja/specs/3-add-spec-explore-mode.md +68 -0
- package/docs/guide/ja/specs/4-drafts-and-contract-layer.md +81 -0
- package/docs/guide/pt/agents/1-meet-the-agents.md +38 -0
- package/docs/guide/pt/agents/2-profiles-and-the-balanced-default.md +45 -0
- package/docs/guide/pt/agents/3-customizing-models-per-agent.md +60 -0
- package/docs/guide/pt/agents/4-custom-agents-catalog.md +43 -0
- package/docs/guide/pt/getting-started/1-what-is-specrails.md +49 -0
- package/docs/guide/pt/getting-started/2-installing-and-first-run.md +42 -0
- package/docs/guide/pt/getting-started/3-adding-your-first-project.md +58 -0
- package/docs/guide/pt/getting-started/4-the-dashboard-tour.md +53 -0
- package/docs/guide/pt/insights/1-analytics-and-cost-tracking.md +78 -0
- package/docs/guide/pt/insights/2-the-integrated-terminal.md +46 -0
- package/docs/guide/pt/insights/3-code-explorer.md +50 -0
- package/docs/guide/pt/integrations/1-ai-providers.md +64 -0
- package/docs/guide/pt/integrations/2-plugins.md +44 -0
- package/docs/guide/pt/integrations/3-jira-integration.md +71 -0
- package/docs/guide/pt/integrations/4-mobile-companion.md +38 -0
- package/docs/guide/pt/pipeline/1-rails-and-jobs.md +94 -0
- package/docs/guide/pt/pipeline/2-the-job-detail-view.md +90 -0
- package/docs/guide/pt/pipeline/3-batch-implement-and-multi-feature.md +78 -0
- package/docs/guide/pt/pipeline/4-picking-an-engine-per-rail.md +60 -0
- package/docs/guide/pt/settings/1-themes.md +37 -0
- package/docs/guide/pt/settings/2-language.md +39 -0
- package/docs/guide/pt/settings/3-pipeline-telemetry-and-diagnostics.md +46 -0
- package/docs/guide/pt/settings/4-where-your-data-lives.md +48 -0
- package/docs/guide/pt/specs/1-specs-and-the-backlog.md +52 -0
- package/docs/guide/pt/specs/2-add-spec-quick-mode.md +45 -0
- package/docs/guide/pt/specs/3-add-spec-explore-mode.md +68 -0
- package/docs/guide/pt/specs/4-drafts-and-contract-layer.md +81 -0
- package/docs/guide/zh/agents/1-meet-the-agents.md +38 -0
- package/docs/guide/zh/agents/2-profiles-and-the-balanced-default.md +45 -0
- package/docs/guide/zh/agents/3-customizing-models-per-agent.md +60 -0
- package/docs/guide/zh/agents/4-custom-agents-catalog.md +43 -0
- package/docs/guide/zh/getting-started/1-what-is-specrails.md +49 -0
- package/docs/guide/zh/getting-started/2-installing-and-first-run.md +42 -0
- package/docs/guide/zh/getting-started/3-adding-your-first-project.md +58 -0
- package/docs/guide/zh/getting-started/4-the-dashboard-tour.md +53 -0
- package/docs/guide/zh/insights/1-analytics-and-cost-tracking.md +78 -0
- package/docs/guide/zh/insights/2-the-integrated-terminal.md +46 -0
- package/docs/guide/zh/insights/3-code-explorer.md +50 -0
- package/docs/guide/zh/integrations/1-ai-providers.md +64 -0
- package/docs/guide/zh/integrations/2-plugins.md +44 -0
- package/docs/guide/zh/integrations/3-jira-integration.md +71 -0
- package/docs/guide/zh/integrations/4-mobile-companion.md +38 -0
- package/docs/guide/zh/pipeline/1-rails-and-jobs.md +94 -0
- package/docs/guide/zh/pipeline/2-the-job-detail-view.md +90 -0
- package/docs/guide/zh/pipeline/3-batch-implement-and-multi-feature.md +78 -0
- package/docs/guide/zh/pipeline/4-picking-an-engine-per-rail.md +60 -0
- package/docs/guide/zh/settings/1-themes.md +37 -0
- package/docs/guide/zh/settings/2-language.md +39 -0
- package/docs/guide/zh/settings/3-pipeline-telemetry-and-diagnostics.md +46 -0
- package/docs/guide/zh/settings/4-where-your-data-lives.md +48 -0
- package/docs/guide/zh/specs/1-specs-and-the-backlog.md +52 -0
- package/docs/guide/zh/specs/2-add-spec-quick-mode.md +45 -0
- package/docs/guide/zh/specs/3-add-spec-explore-mode.md +68 -0
- package/docs/guide/zh/specs/4-drafts-and-contract-layer.md +81 -0
- package/docs/internals/README.md +1 -1
- package/docs/internals/adding-a-provider.md +192 -59
- package/docs/internals/api-reference.md +130 -21
- package/docs/internals/architecture.md +22 -9
- package/docs/internals/bundled-framework-build-plan.md +264 -0
- package/docs/internals/configuration.md +33 -8
- package/docs/internals/global-artifacts-alignment-contract.md +486 -0
- package/docs/internals/global-artifacts-relocation-evaluation.md +294 -0
- package/docs/internals/operations-runbook.md +16 -5
- package/docs/internals/profiles.md +42 -14
- package/docs/platforms/macos.md +27 -8
- package/docs/platforms/windows.md +20 -6
- package/docs/running-pipelines.md +17 -9
- package/docs/terminal.md +9 -3
- package/docs/tracking-cost.md +17 -11
- package/package.json +1 -1
- package/server/dist/agent-refine-manager.js +20 -5
- package/server/dist/artifact-registry.js +468 -0
- package/server/dist/attachment-manager.js +5 -8
- package/server/dist/browser-capture-manager.js +4 -4
- package/server/dist/bundled-core.js +72 -0
- package/server/dist/bundled-openspec.js +58 -0
- package/server/dist/chat-manager.js +42 -5
- package/server/dist/code-explorer-router.js +10 -7
- package/server/dist/config.js +7 -2
- package/server/dist/context-budget.js +17 -6
- package/server/dist/context-scope.js +6 -2
- package/server/dist/contract-refine-runner.js +31 -9
- package/server/dist/desktop-router.js +39 -14
- package/server/dist/docs-router.js +210 -132
- package/server/dist/file-summary-manager.js +41 -16
- package/server/dist/framework-manager.js +248 -0
- package/server/dist/framework-migration.js +308 -0
- package/server/dist/index.js +30 -0
- package/server/dist/install-config-path.js +73 -0
- package/server/dist/jira/jira-sync-manager.js +23 -11
- package/server/dist/openspec-shim.js +153 -0
- package/server/dist/plugins-router.js +19 -8
- package/server/dist/profiles-router.js +38 -16
- package/server/dist/project-registry.js +101 -3
- package/server/dist/project-router-chat.js +1 -1
- package/server/dist/project-router-helpers.js +25 -12
- package/server/dist/project-router-jobs.js +3 -3
- package/server/dist/project-router-settings.js +8 -6
- package/server/dist/project-router-setup.js +27 -10
- package/server/dist/project-router-spending.js +6 -1
- package/server/dist/project-router-tickets.js +30 -10
- package/server/dist/project-router.js +16 -1
- package/server/dist/providers/gemini-adapter.js +4 -0
- package/server/dist/providers/gemini-agent-ack.js +65 -0
- package/server/dist/queue-manager.js +156 -12
- package/server/dist/setup-manager.js +131 -29
- package/server/dist/smash-runner.js +21 -6
- package/server/dist/ticket-store.js +6 -2
- package/server/dist/ticket-watcher.js +5 -1
- package/server/dist/util/stream-display.js +18 -3
- package/server/dist/vitest-setup.js +25 -0
- package/server/dist/workspace-manager.js +199 -0
- package/server/dist/workspace-resolution.js +147 -0
- package/client/dist/assets/index-DgKfQFcf.css +0 -2
- package/client/dist/assets/setup-BIIkb-_K.js +0 -1
- package/client/dist/assets/setup-BeQxu9kD.js +0 -1
- package/client/dist/assets/setup-CPa6GnlI.js +0 -1
- package/client/dist/assets/setup-CZl4OEJx.js +0 -1
- package/client/dist/assets/setup-ChpodNfn.js +0 -1
- package/client/dist/assets/setup-D_fjJH6u.js +0 -1
- package/client/dist/assets/setup-YzD8DX4O.js +0 -1
- package/client/dist/assets/setup-fRpDozmq.js +0 -1
- package/docs/adding-a-provider.md +0 -107
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Gemini headless subagent pre-acknowledgment.
|
|
3
|
+
//
|
|
4
|
+
// gemini 0.46+ DISCOVERS `<project>/.gemini/agents/*.md` but only ENABLES a
|
|
5
|
+
// project's custom subagents after an interactive "New Agents Discovered →
|
|
6
|
+
// Acknowledge and Enable" prompt. That prompt never fires in headless
|
|
7
|
+
// (`gemini -p`) spawns — which is how the desktop runs every rail — so
|
|
8
|
+
// `invoke_agent sr-architect` returns "Subagent not found" and the implement
|
|
9
|
+
// orchestrator silently falls back to a generic agent (the specialised
|
|
10
|
+
// architect/developer/reviewer personas never run in isolation).
|
|
11
|
+
//
|
|
12
|
+
// specrails-core writes the acknowledgment file at install time; this is the
|
|
13
|
+
// defence-in-depth copy the desktop runs right before a gemini rail spawn, so a
|
|
14
|
+
// project installed with an older core (or whose agents changed since install)
|
|
15
|
+
// is still trusted headless. The file gemini reads is
|
|
16
|
+
// `~/.gemini/acknowledgments/agents.json`, shaped
|
|
17
|
+
// { [projectRoot]: { [agentName]: <sha256-hex of the agent .md file> } }
|
|
18
|
+
// where the hash is sha256 of the FULL agent markdown file (verified empirically
|
|
19
|
+
// against gemini 0.47). Entries are MERGED so other projects (and other agents)
|
|
20
|
+
// survive. Best-effort — callers swallow any error; a failure only means the
|
|
21
|
+
// agents need the one-time interactive acknowledge.
|
|
22
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
+
exports.acknowledgeGeminiProjectAgents = acknowledgeGeminiProjectAgents;
|
|
24
|
+
const crypto_1 = require("crypto");
|
|
25
|
+
const fs_1 = require("fs");
|
|
26
|
+
const os_1 = require("os");
|
|
27
|
+
const path_1 = require("path");
|
|
28
|
+
function ackFilePath() {
|
|
29
|
+
return (0, path_1.join)((0, os_1.homedir)(), '.gemini', 'acknowledgments', 'agents.json');
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Pre-acknowledge every `<projectPath>/.gemini/agents/*.md` so gemini loads them
|
|
33
|
+
* in headless mode. No-op when the project has no `.gemini/agents` dir or no
|
|
34
|
+
* agent files. The `projectPath` is the key gemini uses (the spawn cwd / repo
|
|
35
|
+
* root), matching what `specrails-core` writes at install.
|
|
36
|
+
*/
|
|
37
|
+
function acknowledgeGeminiProjectAgents(projectPath) {
|
|
38
|
+
const agentsDir = (0, path_1.join)(projectPath, '.gemini', 'agents');
|
|
39
|
+
if (!(0, fs_1.existsSync)(agentsDir))
|
|
40
|
+
return;
|
|
41
|
+
const agentFiles = (0, fs_1.readdirSync)(agentsDir).filter((f) => f.endsWith('.md') && !f.startsWith('_'));
|
|
42
|
+
if (agentFiles.length === 0)
|
|
43
|
+
return;
|
|
44
|
+
const ackPath = ackFilePath();
|
|
45
|
+
let store = {};
|
|
46
|
+
if ((0, fs_1.existsSync)(ackPath)) {
|
|
47
|
+
try {
|
|
48
|
+
const parsed = JSON.parse((0, fs_1.readFileSync)(ackPath, 'utf8'));
|
|
49
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
50
|
+
store = parsed;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// Corrupt/unreadable file — start fresh rather than crash the spawn.
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const projectEntry = { ...(store[projectPath] ?? {}) };
|
|
58
|
+
for (const file of agentFiles) {
|
|
59
|
+
const content = (0, fs_1.readFileSync)((0, path_1.join)(agentsDir, file), 'utf8');
|
|
60
|
+
projectEntry[file.slice(0, -3)] = (0, crypto_1.createHash)('sha256').update(content).digest('hex');
|
|
61
|
+
}
|
|
62
|
+
store[projectPath] = projectEntry;
|
|
63
|
+
(0, fs_1.mkdirSync)((0, path_1.join)((0, os_1.homedir)(), '.gemini', 'acknowledgments'), { recursive: true });
|
|
64
|
+
(0, fs_1.writeFileSync)(ackPath, `${JSON.stringify(store, null, 2)}\n`);
|
|
65
|
+
}
|
|
@@ -28,6 +28,10 @@ const interactive_job_session_1 = require("./interactive-job-session");
|
|
|
28
28
|
const attachment_manager_1 = require("./attachment-manager");
|
|
29
29
|
const ticket_store_1 = require("./ticket-store");
|
|
30
30
|
const binary_probe_1 = require("./binary-probe");
|
|
31
|
+
const workspace_resolution_1 = require("./workspace-resolution");
|
|
32
|
+
const framework_manager_1 = require("./framework-manager");
|
|
33
|
+
const openspec_shim_1 = require("./openspec-shim");
|
|
34
|
+
const artifact_registry_1 = require("./artifact-registry");
|
|
31
35
|
// ─── Telemetry env helpers ────────────────────────────────────────────────────
|
|
32
36
|
/** Build the OTEL environment variable block for a spawned claude process.
|
|
33
37
|
* Extracted as a pure function so it is unit-testable without a full spawn. */
|
|
@@ -156,6 +160,13 @@ class QueueManager {
|
|
|
156
160
|
/** Pre-spawn working-tree snapshot refs keyed by jobId — read at exit time
|
|
157
161
|
* by the Code-Explorer provenance hook. Cleared on job exit. */
|
|
158
162
|
_snapshotRefs;
|
|
163
|
+
/** Per-job resolved execution context (relocate-artifacts gate), captured at
|
|
164
|
+
* spawn time so `_onJobExit`'s provenance hook uses the SAME repoDir the
|
|
165
|
+
* snapshot used (= project.path, never the workspace). Cleared on job exit. */
|
|
166
|
+
_jobExecution;
|
|
167
|
+
/** Per-job openspec PATH shim dir (relocated claude rails only). Cleaned up on
|
|
168
|
+
* job exit. In-memory map of jobId → shim dir. */
|
|
169
|
+
_openspecShims = new Map();
|
|
159
170
|
/** Pending per-job interactive flag keyed by jobId — read at spawn time.
|
|
160
171
|
* In-memory only (mirrors _jobModelSelection). */
|
|
161
172
|
_jobInteractiveSelection;
|
|
@@ -190,6 +201,7 @@ class QueueManager {
|
|
|
190
201
|
this._jobProviderSelection = new Map();
|
|
191
202
|
this._jobModelSelection = new Map();
|
|
192
203
|
this._snapshotRefs = new Map();
|
|
204
|
+
this._jobExecution = new Map();
|
|
193
205
|
this._jobInteractiveSelection = new Map();
|
|
194
206
|
this._interactiveSessions = new Map();
|
|
195
207
|
const envTimeout = process.env.WM_ZOMBIE_TIMEOUT_MS !== undefined
|
|
@@ -261,6 +273,8 @@ class QueueManager {
|
|
|
261
273
|
this._interactiveSessions.clear();
|
|
262
274
|
// Release any per-job provenance snapshots so teardown leaves no map entries.
|
|
263
275
|
this._snapshotRefs.clear();
|
|
276
|
+
this._jobExecution.clear();
|
|
277
|
+
this._openspecShims.clear();
|
|
264
278
|
// Drop the DB reference last so any in-flight 'close' callback sees null
|
|
265
279
|
// and skips all DB work via the existing `if (this._db)` guards.
|
|
266
280
|
this._db = null;
|
|
@@ -522,7 +536,7 @@ class QueueManager {
|
|
|
522
536
|
if (ticketIds.length === 0)
|
|
523
537
|
return '';
|
|
524
538
|
try {
|
|
525
|
-
const store = (0, ticket_store_1.readStore)(
|
|
539
|
+
const store = (0, ticket_store_1.readStore)(this._resolveTicketsPath());
|
|
526
540
|
const sections = [];
|
|
527
541
|
for (const ticketId of ticketIds) {
|
|
528
542
|
const storeAttachmentIds = new Set((store.tickets[String(ticketId)]?.attachments ?? []).map((attachment) => attachment.id));
|
|
@@ -563,7 +577,7 @@ class QueueManager {
|
|
|
563
577
|
const specs = [];
|
|
564
578
|
if (this._cwd) {
|
|
565
579
|
try {
|
|
566
|
-
const store = (0, ticket_store_1.readStore)(
|
|
580
|
+
const store = (0, ticket_store_1.readStore)(this._resolveTicketsPath());
|
|
567
581
|
for (const ticketId of ticketIds) {
|
|
568
582
|
const ticket = store.tickets[String(ticketId)];
|
|
569
583
|
if (!ticket)
|
|
@@ -634,6 +648,46 @@ class QueueManager {
|
|
|
634
648
|
}
|
|
635
649
|
return this._adapter;
|
|
636
650
|
}
|
|
651
|
+
/**
|
|
652
|
+
* Resolve the relocate-artifacts execution context for this manager's project.
|
|
653
|
+
* The gate: relocated only when a registry entry exists AND core has populated
|
|
654
|
+
* the workspace; otherwise legacy (cwd = project.path, empty env) — preserving
|
|
655
|
+
* byte-identical behaviour for every existing in-repo project. Falls back to a
|
|
656
|
+
* legacy resolution rooted at `this._cwd` when slug/cwd are unavailable
|
|
657
|
+
* (non-Super contexts / tests that construct the manager without a slug).
|
|
658
|
+
*/
|
|
659
|
+
_resolveExecution() {
|
|
660
|
+
const repoDir = this._cwd ?? process.cwd();
|
|
661
|
+
if (!this._projectSlug || !this._cwd) {
|
|
662
|
+
return {
|
|
663
|
+
relocated: false,
|
|
664
|
+
cwd: repoDir,
|
|
665
|
+
repoDir,
|
|
666
|
+
workspaceDir: null,
|
|
667
|
+
ticketsPath: path_1.default.join(repoDir, '.specrails', 'local-tickets.json'),
|
|
668
|
+
backlogConfigPath: path_1.default.join(repoDir, '.specrails', 'backlog-config.json'),
|
|
669
|
+
profilesDir: path_1.default.join(repoDir, '.specrails', 'profiles'),
|
|
670
|
+
pluginsStateDir: path_1.default.join(repoDir, '.specrails', 'plugins'),
|
|
671
|
+
fileSummariesDir: path_1.default.join(repoDir, '.specrails', 'file-summaries'),
|
|
672
|
+
specrailsDir: path_1.default.join(repoDir, '.specrails'),
|
|
673
|
+
stateDir: path_1.default.join(repoDir, '.claude'),
|
|
674
|
+
env: {},
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
return (0, workspace_resolution_1.resolveProjectExecution)({ slug: this._projectSlug, path: this._cwd });
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* Resolve the local-tickets.json path honouring the relocate-artifacts gate.
|
|
681
|
+
* Relocated ⇒ the registry entry's ticketsPath (workspace). Legacy ⇒
|
|
682
|
+
* `resolveTicketStoragePath(this._cwd)` which preserves the
|
|
683
|
+
* integration-contract.json custom-storagePath behaviour for existing repos.
|
|
684
|
+
*/
|
|
685
|
+
_resolveTicketsPath() {
|
|
686
|
+
const exec = this._resolveExecution();
|
|
687
|
+
if (exec.relocated)
|
|
688
|
+
return exec.ticketsPath;
|
|
689
|
+
return (0, ticket_store_1.resolveTicketStoragePath)(this._cwd ?? process.cwd());
|
|
690
|
+
}
|
|
637
691
|
/**
|
|
638
692
|
* Spawn an interactive ultracode session. The job row is created with the
|
|
639
693
|
* `interactive` flag set; the resident child runs the first turn (the
|
|
@@ -687,6 +741,13 @@ class QueueManager {
|
|
|
687
741
|
// Interactive jobs skip provenance, but a defensive delete keeps the map
|
|
688
742
|
// clean if a snapshot was ever recorded for this id.
|
|
689
743
|
this._snapshotRefs.delete(jobId);
|
|
744
|
+
// Clean up the per-job openspec PATH shim (relocated claude rails only).
|
|
745
|
+
const shim = this._openspecShims.get(jobId);
|
|
746
|
+
if (shim) {
|
|
747
|
+
this._openspecShims.delete(jobId);
|
|
748
|
+
if (this._projectSlug)
|
|
749
|
+
(0, openspec_shim_1.removeOpenspecShim)(this._projectSlug, jobId, (0, artifact_registry_1.resolveHome)());
|
|
750
|
+
}
|
|
690
751
|
if (this._disposed)
|
|
691
752
|
return;
|
|
692
753
|
const job = this._jobs.get(jobId);
|
|
@@ -785,6 +846,13 @@ class QueueManager {
|
|
|
785
846
|
// primary; everything in this spawn (binary, argv, model, profile, OTEL,
|
|
786
847
|
// plugins, result parsing, ai_invocations.provider) flows from `adapter`.
|
|
787
848
|
const adapter = this._resolveJobAdapter(jobId);
|
|
849
|
+
// Relocate-artifacts gate: resolve cwd/repoDir/env for this spawn. Legacy
|
|
850
|
+
// projects get cwd = project.path + empty env (byte-identical to today);
|
|
851
|
+
// relocated projects get cwd = workspace + SPECRAILS_REPO_DIR. Captured per
|
|
852
|
+
// job so the post-exit provenance hook uses the SAME repoDir.
|
|
853
|
+
const execution = this._resolveExecution();
|
|
854
|
+
this._jobExecution.set(jobId, execution);
|
|
855
|
+
const spawnCwd = execution.cwd;
|
|
788
856
|
job.status = 'running';
|
|
789
857
|
job.startedAt = new Date().toISOString();
|
|
790
858
|
job.queuePosition = null;
|
|
@@ -875,10 +943,17 @@ class QueueManager {
|
|
|
875
943
|
: adapter.id === 'claude' && this._db
|
|
876
944
|
? (0, db_1.getProjectSettings)(this._db).orchestratorModel
|
|
877
945
|
: (this._resolvedModel ?? adapter.defaultModel());
|
|
946
|
+
// Relocate-artifacts: when relocated, claude is spawned from the workspace
|
|
947
|
+
// so add `--add-dir <repoDir>` so its tools can still reach repo files by
|
|
948
|
+
// absolute path. (gemini/codex get env-only tweaks at spawn time below.)
|
|
949
|
+
const railExtraArgs = execution.relocated && adapter.id === 'claude'
|
|
950
|
+
? ['--add-dir', execution.repoDir]
|
|
951
|
+
: undefined;
|
|
878
952
|
const args = adapter.buildArgs('rail-job', {
|
|
879
953
|
prompt: railPrompt,
|
|
880
954
|
systemPrompt: systemAppend || undefined,
|
|
881
955
|
model: railModel,
|
|
956
|
+
extraArgs: railExtraArgs,
|
|
882
957
|
});
|
|
883
958
|
// Resolve agent profile (if any) and snapshot per-job before spawn.
|
|
884
959
|
// Super mode only (projectId + projectSlug + cwd all present).
|
|
@@ -894,11 +969,13 @@ class QueueManager {
|
|
|
894
969
|
try {
|
|
895
970
|
const selection = this._jobProfileSelection.get(jobId); // undefined|null|string
|
|
896
971
|
this._jobProfileSelection.delete(jobId);
|
|
897
|
-
|
|
972
|
+
// When relocated, core + `.specrails/profiles` live in the workspace
|
|
973
|
+
// (execution.cwd); legacy reads from the repo (execution.cwd === repo).
|
|
974
|
+
const coreSupports = projectSupportsProfiles(execution.cwd);
|
|
898
975
|
if (selection !== null && coreSupports) {
|
|
899
976
|
// selection is string (explicit) or undefined (default resolution)
|
|
900
977
|
const { resolveProfile, snapshotForJob, persistJobProfile, } = require('./profile-manager');
|
|
901
|
-
const resolved = resolveProfile(
|
|
978
|
+
const resolved = resolveProfile(execution.cwd, selection ?? undefined, adapter.id);
|
|
902
979
|
if (resolved) {
|
|
903
980
|
profileSnapshotPath = snapshotForJob(this._projectSlug, jobId, resolved);
|
|
904
981
|
profileName = resolved.name;
|
|
@@ -921,12 +998,22 @@ class QueueManager {
|
|
|
921
998
|
// gets signals synthesised by the codex-otel-bridge attached below.
|
|
922
999
|
let spawnEnv = process.env;
|
|
923
1000
|
const telemetryEnabled = !!(this._projectId && this._db && (0, db_1.getProjectSettings)(this._db).pipelineTelemetryEnabled);
|
|
1001
|
+
// Resolve the framework version ONCE at spawn time — `framework/current`
|
|
1002
|
+
// is read from `~/.specrails/framework/current`. A concurrent atomic swap
|
|
1003
|
+
// (FrameworkManager.swapCurrent) does not disturb this job: we captured the
|
|
1004
|
+
// version here and the per-job snapshot resolved its handles from this
|
|
1005
|
+
// value. GATED on `execution.relocated`: a LEGACY (in-repo) job does NOT
|
|
1006
|
+
// assemble from `framework/current`, so stamping it with a sibling project's
|
|
1007
|
+
// materialized framework version would be wrong telemetry. Null otherwise.
|
|
1008
|
+
const frameworkVersion = execution.relocated ? (0, framework_manager_1.readCurrentFrameworkVersion)() : null;
|
|
924
1009
|
if (telemetryEnabled && adapter.capabilities.nativeOtelEnv && this._projectId) {
|
|
925
1010
|
const extra = {};
|
|
926
1011
|
if (profileName)
|
|
927
1012
|
extra['specrails.profile_name'] = profileName;
|
|
928
1013
|
if (profileName)
|
|
929
1014
|
extra['specrails.profile_schema_version'] = '1';
|
|
1015
|
+
if (frameworkVersion)
|
|
1016
|
+
extra['specrails.framework_version'] = frameworkVersion;
|
|
930
1017
|
spawnEnv = {
|
|
931
1018
|
...process.env,
|
|
932
1019
|
...buildTelemetryEnv(jobId, this._projectId, this._desktopPort, extra),
|
|
@@ -952,7 +1039,8 @@ class QueueManager {
|
|
|
952
1039
|
if (adapter.mcpRegistration === 'project-json' && this._projectId && this._projectSlug && this._cwd) {
|
|
953
1040
|
try {
|
|
954
1041
|
const { resolvePluginsForSpawn, snapshotPluginsForJob } = require('./plugins/rail-integration');
|
|
955
|
-
|
|
1042
|
+
// Relocated ⇒ `.mcp.json`/plugin state live in the workspace (execution.cwd).
|
|
1043
|
+
const resolution = await resolvePluginsForSpawn(execution.cwd, this._projectId, jobId);
|
|
956
1044
|
pluginActive = resolution.active;
|
|
957
1045
|
pluginDegraded = resolution.degraded;
|
|
958
1046
|
if (pluginActive.length > 0 || pluginDegraded.length > 0) {
|
|
@@ -987,6 +1075,14 @@ class QueueManager {
|
|
|
987
1075
|
const settings = (0, db_1.getProjectSettings)(this._db);
|
|
988
1076
|
if (settings.pipelineTelemetryEnabled && (pluginActive.length > 0 || pluginDegraded.length > 0)) {
|
|
989
1077
|
const extra = {};
|
|
1078
|
+
// Re-thread the resolved framework version (this block rebuilds the
|
|
1079
|
+
// whole telemetry env, so it must carry the same attr as the first one).
|
|
1080
|
+
if (frameworkVersion)
|
|
1081
|
+
extra['specrails.framework_version'] = frameworkVersion;
|
|
1082
|
+
if (profileName)
|
|
1083
|
+
extra['specrails.profile_name'] = profileName;
|
|
1084
|
+
if (profileName)
|
|
1085
|
+
extra['specrails.profile_schema_version'] = '1';
|
|
990
1086
|
if (pluginActive.length > 0) {
|
|
991
1087
|
extra['specrails.plugins.active'] = JSON.stringify(pluginActive.map((p) => p.name));
|
|
992
1088
|
extra['specrails.plugins.versions'] = JSON.stringify(Object.fromEntries(pluginActive.map((p) => [p.name, p.version])));
|
|
@@ -1000,6 +1096,44 @@ class QueueManager {
|
|
|
1000
1096
|
};
|
|
1001
1097
|
}
|
|
1002
1098
|
}
|
|
1099
|
+
// Provider-specific filesystem prep before a headless rail spawn. Gemini
|
|
1100
|
+
// uses this to pre-acknowledge the project's custom subagents so they load
|
|
1101
|
+
// in `gemini -p` mode (else invoke_agent reports "Subagent not found" and the
|
|
1102
|
+
// orchestrator silently falls back to a generic agent). No-op for claude/codex.
|
|
1103
|
+
// Runs in the SPAWN cwd (workspace when relocated) — gemini acks the
|
|
1104
|
+
// project's subagents where it will actually discover them.
|
|
1105
|
+
try {
|
|
1106
|
+
adapter.prepareHeadlessSpawn?.(spawnCwd);
|
|
1107
|
+
}
|
|
1108
|
+
catch (err) {
|
|
1109
|
+
/* c8 ignore next -- best-effort prep; a failure is non-fatal */
|
|
1110
|
+
console.warn(`[queue-manager] headless-spawn prep failed: ${err.message}`);
|
|
1111
|
+
}
|
|
1112
|
+
// ─── Relocate-artifacts spawn env ──────────────────────────────────────
|
|
1113
|
+
// Merge SPECRAILS_REPO_DIR (+ workspace/tickets/state/etc.) so stage-3
|
|
1114
|
+
// `${SPECRAILS_REPO_DIR:-.}` re-pointing drives source/openspec/git I/O back
|
|
1115
|
+
// into the repo. Per provider: gemini trusts the workspace cwd; codex gets
|
|
1116
|
+
// NO CODEX_HOME override (all-or-nothing incl. auth → breaks the rail) and
|
|
1117
|
+
// relies on cwd-based discovery from the workspace. Legacy ⇒ empty env, no-op.
|
|
1118
|
+
if (execution.relocated) {
|
|
1119
|
+
spawnEnv = { ...spawnEnv, ...execution.env };
|
|
1120
|
+
if (adapter.id === 'gemini') {
|
|
1121
|
+
spawnEnv = { ...spawnEnv, GEMINI_CLI_TRUST_WORKSPACE: 'true' };
|
|
1122
|
+
}
|
|
1123
|
+
// openspec PATH shim (claude rails only): prepend a per-job shim dir that
|
|
1124
|
+
// re-points every BARE `openspec` call at the repo working tree, so a
|
|
1125
|
+
// skill- or un-wrapped-template-driven `openspec <verb>` from the workspace
|
|
1126
|
+
// cwd still operates on the repo's OpenSpec project (see openspec-shim.ts).
|
|
1127
|
+
// claude is the only adapter that runs the openspec-backed sr-* rails;
|
|
1128
|
+
// gemini/codex skill scaffolds carry their own repo-dir wrapping.
|
|
1129
|
+
if (adapter.id === 'claude' && this._projectSlug) {
|
|
1130
|
+
const shimDir = (0, openspec_shim_1.ensureOpenspecShim)(this._projectSlug, jobId, (0, artifact_registry_1.resolveHome)());
|
|
1131
|
+
if (shimDir) {
|
|
1132
|
+
this._openspecShims.set(jobId, shimDir);
|
|
1133
|
+
spawnEnv = { ...spawnEnv, PATH: (0, openspec_shim_1.prependShimToPath)(spawnEnv.PATH, shimDir) };
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1003
1137
|
// ─── Interactive ultracode branch ──────────────────────────────────────
|
|
1004
1138
|
// When the launch requested interactive mode AND the command is ultracode
|
|
1005
1139
|
// AND the adapter supports persistent stdin (claude), hand off to a resident
|
|
@@ -1015,16 +1149,20 @@ class QueueManager {
|
|
|
1015
1149
|
prompt: '',
|
|
1016
1150
|
systemPrompt: systemAppend || undefined,
|
|
1017
1151
|
model: railModel,
|
|
1152
|
+
extraArgs: railExtraArgs,
|
|
1018
1153
|
});
|
|
1019
|
-
this._startInteractiveJob(jobId, job, adapter, { binary, args: interactiveArgs, cwd:
|
|
1154
|
+
this._startInteractiveJob(jobId, job, adapter, { binary, args: interactiveArgs, cwd: spawnCwd, env: spawnEnv }, railPrompt);
|
|
1020
1155
|
return;
|
|
1021
1156
|
}
|
|
1022
1157
|
// Code-Explorer pre-spawn snapshot. Captures the working-tree state via
|
|
1023
1158
|
// `git stash create --include-untracked` so the post-exit hook can diff
|
|
1024
1159
|
// against it. Gated by SPECRAILS_CODE_EXPLORER — when off, no-op.
|
|
1025
|
-
|
|
1160
|
+
// CRITICAL: snapshot the REPO working tree (execution.repoDir), never the
|
|
1161
|
+
// workspace — else a relocated job would diff an empty workspace and silently
|
|
1162
|
+
// record zero "touched by AI" files.
|
|
1163
|
+
if ((0, feature_flags_1.isCodeExplorerEnabled)()) {
|
|
1026
1164
|
try {
|
|
1027
|
-
const snap = (0, file_provenance_1.snapshotWorkingTree)(
|
|
1165
|
+
const snap = (0, file_provenance_1.snapshotWorkingTree)(execution.repoDir);
|
|
1028
1166
|
this._snapshotRefs.set(jobId, snap);
|
|
1029
1167
|
}
|
|
1030
1168
|
catch (err) {
|
|
@@ -1035,7 +1173,7 @@ class QueueManager {
|
|
|
1035
1173
|
const child = (0, cli_prompt_1.spawnAiCli)(binary, args, {
|
|
1036
1174
|
env: spawnEnv,
|
|
1037
1175
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
1038
|
-
cwd:
|
|
1176
|
+
cwd: spawnCwd,
|
|
1039
1177
|
});
|
|
1040
1178
|
this._activeProcess = child;
|
|
1041
1179
|
this._activeJobId = jobId;
|
|
@@ -1224,6 +1362,12 @@ class QueueManager {
|
|
|
1224
1362
|
// stash commit it references is dangling and git-GC'd on its own).
|
|
1225
1363
|
const snapshot = this._snapshotRefs.get(jobId);
|
|
1226
1364
|
this._snapshotRefs.delete(jobId);
|
|
1365
|
+
// Relocate-artifacts: the repo dir this job snapshotted against (= repoDir,
|
|
1366
|
+
// never the workspace). Falls back to this._cwd for jobs spawned before this
|
|
1367
|
+
// map existed (e.g. restored-from-db) so provenance still targets the repo.
|
|
1368
|
+
const jobExecution = this._jobExecution.get(jobId);
|
|
1369
|
+
this._jobExecution.delete(jobId);
|
|
1370
|
+
const provenanceRepoDir = jobExecution?.repoDir ?? this._cwd;
|
|
1227
1371
|
// A3: release the active slot for THIS job before any early return, so a
|
|
1228
1372
|
// disposed/unknown-job exit can never leave the slot reserved (which would
|
|
1229
1373
|
// wedge the queue). Guarded by identity in case a stale exit fires late.
|
|
@@ -1328,11 +1472,11 @@ class QueueManager {
|
|
|
1328
1472
|
// the pre-spawn snapshot and inserts one row per touched path. Gated by
|
|
1329
1473
|
// SPECRAILS_CODE_EXPLORER (re-checked at each completion so the flag can
|
|
1330
1474
|
// be flipped off mid-session without leaving partial writes).
|
|
1331
|
-
if ((0, feature_flags_1.isCodeExplorerEnabled)() &&
|
|
1475
|
+
if ((0, feature_flags_1.isCodeExplorerEnabled)() && provenanceRepoDir && this._projectId) {
|
|
1332
1476
|
const ref = snapshot?.ref ?? '';
|
|
1333
1477
|
try {
|
|
1334
|
-
const diff = (0, file_provenance_1.diffAgainstSnapshot)(
|
|
1335
|
-
const patches = (0, file_provenance_1.collectDiffPatches)(
|
|
1478
|
+
const diff = (0, file_provenance_1.diffAgainstSnapshot)(provenanceRepoDir, ref, snapshot?.untracked, snapshot?.headSha);
|
|
1479
|
+
const patches = (0, file_provenance_1.collectDiffPatches)(provenanceRepoDir, ref, diff, snapshot?.headSha);
|
|
1336
1480
|
if (diff.length > 50) {
|
|
1337
1481
|
console.warn(`[provenance.large_job] job=${jobId} files=${diff.length}`);
|
|
1338
1482
|
}
|
|
@@ -20,6 +20,11 @@ const win_spawn_1 = require("./util/win-spawn");
|
|
|
20
20
|
const setup_prerequisites_1 = require("./setup-prerequisites");
|
|
21
21
|
const core_package_1 = require("./core-package");
|
|
22
22
|
const providers_1 = require("./providers");
|
|
23
|
+
const artifact_registry_1 = require("./artifact-registry");
|
|
24
|
+
const install_config_path_1 = require("./install-config-path");
|
|
25
|
+
const bundled_core_1 = require("./bundled-core");
|
|
26
|
+
const bundled_openspec_1 = require("./bundled-openspec");
|
|
27
|
+
const framework_manager_1 = require("./framework-manager");
|
|
23
28
|
/**
|
|
24
29
|
* specrails-core's installer (Node-native from v4.2.0 onward, bash
|
|
25
30
|
* prior) always scaffolds into `.claude/` regardless of which AI
|
|
@@ -75,6 +80,45 @@ function spawnCoreInit(args, cwd) {
|
|
|
75
80
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
76
81
|
});
|
|
77
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* Spawn the BUNDLED specrails-core `init` (offline framework + assemble; openspec
|
|
85
|
+
* is the only network step). Runs `node <bundled-cli> init <args>` with
|
|
86
|
+
* `SPECRAILS_CORE_SCRIPT_DIR` pointed at the bundle so core's template/command
|
|
87
|
+
* sources resolve from the app bundle, NOT a global install or npm registry. The
|
|
88
|
+
* framework was already materialized by FrameworkManager.materialize() (idempotent
|
|
89
|
+
* — core's ensureFramework skips re-materialization), so this call only assembles
|
|
90
|
+
* the workspace by symlink + runs openspec init.
|
|
91
|
+
*
|
|
92
|
+
* Returns null when no bundled core is present (caller falls back to spawnCoreInit).
|
|
93
|
+
*/
|
|
94
|
+
function spawnBundledCoreInit(args, cwd) {
|
|
95
|
+
const cli = (0, bundled_core_1.getBundledCoreCli)();
|
|
96
|
+
const coreRoot = (0, bundled_core_1.getBundledCoreRoot)();
|
|
97
|
+
if (!cli || !coreRoot)
|
|
98
|
+
return null;
|
|
99
|
+
const fullArgs = [cli, 'init', ...args];
|
|
100
|
+
const env = { ...process.env, SPECRAILS_CORE_SCRIPT_DIR: coreRoot };
|
|
101
|
+
// Bundled openspec (offline) — the LAST network step of project-add. When the
|
|
102
|
+
// app ships @fission-ai/openspec, point specrails-core's `installOpenSpecProject`
|
|
103
|
+
// at the bundled CLI and the SAME node we run the bundled core with. Tauri
|
|
104
|
+
// strips exec bits from bundled resources and the openspec CLI is a node script,
|
|
105
|
+
// so it MUST be invoked as `node <cli> init …` — hence we set BOTH the BIN and
|
|
106
|
+
// the NODE env (specrails-core's buildOpenSpecInvocation form 1). When absent we
|
|
107
|
+
// leave them unset → core falls back to `npx @fission-ai/openspec` (still works
|
|
108
|
+
// online). Bundled core + bundled openspec ⇒ project-add is FULLY OFFLINE.
|
|
109
|
+
const openspecCli = (0, bundled_openspec_1.getBundledOpenspecCli)();
|
|
110
|
+
if (openspecCli) {
|
|
111
|
+
env.SPECRAILS_OPENSPEC_BIN = openspecCli;
|
|
112
|
+
env.SPECRAILS_OPENSPEC_NODE = process.execPath;
|
|
113
|
+
}
|
|
114
|
+
console.log(`[SetupManager] spawning BUNDLED core: ${process.execPath} ${fullArgs.join(' ')} (cwd=${cwd})` +
|
|
115
|
+
(openspecCli ? ' [bundled openspec: offline]' : ' [openspec: npx fallback]'));
|
|
116
|
+
return (0, win_spawn_1.spawnCli)(process.execPath, fullArgs, {
|
|
117
|
+
cwd,
|
|
118
|
+
env,
|
|
119
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
120
|
+
});
|
|
121
|
+
}
|
|
78
122
|
// H19: hard cap on the runtime probe. `npx --yes --prefer-online
|
|
79
123
|
// <CORE_PACKAGE_SPEC> version` does a network round-trip to the npm
|
|
80
124
|
// registry, and spawnSync blocks the single event loop — without a timeout a
|
|
@@ -120,8 +164,8 @@ function writeSpawnInstallConfig(projectId, yamlText) {
|
|
|
120
164
|
(0, fs_1.writeFileSync)(tempPath, yamlText, 'utf-8');
|
|
121
165
|
return tempPath;
|
|
122
166
|
}
|
|
123
|
-
function readInstallConfig(
|
|
124
|
-
const configPath = (0,
|
|
167
|
+
function readInstallConfig(project) {
|
|
168
|
+
const configPath = (0, install_config_path_1.installConfigPath)(project);
|
|
125
169
|
try {
|
|
126
170
|
const text = (0, fs_1.readFileSync)(configPath, 'utf-8');
|
|
127
171
|
const tierMatch = text.match(/^tier:\s*(\w+)/m);
|
|
@@ -591,14 +635,33 @@ class SetupManager {
|
|
|
591
635
|
this._projectNames = new Map();
|
|
592
636
|
}
|
|
593
637
|
// ─── Full Install: TUI installer (npx specrails-core) ────────────────────────
|
|
594
|
-
startInstall(projectId, projectPath) {
|
|
638
|
+
startInstall(projectId, projectPath, projectSlug) {
|
|
595
639
|
if (this._installProcesses.has(projectId)) {
|
|
596
640
|
console.warn(`[SetupManager] install already running for ${projectId}`);
|
|
597
641
|
return;
|
|
598
642
|
}
|
|
599
|
-
|
|
643
|
+
// Relocate-artifacts: ensure the shared registry entry exists (slug
|
|
644
|
+
// agreement) BEFORE core's `init` runs, so core resolves its workspace from
|
|
645
|
+
// the registry and installs the relocated artifacts under desktop's slug.
|
|
646
|
+
// addProject already mirrors the entry; this is belt-and-suspenders for the
|
|
647
|
+
// (rare) case where the project was registered before the mirror existed.
|
|
648
|
+
// Wrapped so a registry write failure never blocks install (core then
|
|
649
|
+
// installs in-repo and the project simply stays legacy — gate-safe).
|
|
650
|
+
if (projectSlug) {
|
|
651
|
+
try {
|
|
652
|
+
(0, artifact_registry_1.mirrorProjectEntry)({ repoPath: projectPath, slug: projectSlug, desktopProjectId: projectId });
|
|
653
|
+
}
|
|
654
|
+
catch (err) {
|
|
655
|
+
console.warn(`[SetupManager] registry mirror before install failed (non-fatal): ${err.message}`);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
// Relocate-artifacts: the install config lives in the per-project HOME dir
|
|
659
|
+
// (NOT the repo). `--from-config` accepts any path, so the spawn below points
|
|
660
|
+
// core at the relocated location.
|
|
661
|
+
const installProject = { slug: projectSlug, path: projectPath };
|
|
662
|
+
const configPath = (0, install_config_path_1.installConfigPath)(installProject);
|
|
600
663
|
const hasConfig = (0, fs_1.existsSync)(configPath);
|
|
601
|
-
const parsedConfig = hasConfig ? readInstallConfig(
|
|
664
|
+
const parsedConfig = hasConfig ? readInstallConfig(installProject) : null;
|
|
602
665
|
const tier = parsedConfig?.tier ?? 'full';
|
|
603
666
|
this._projectTiers.set(projectId, tier);
|
|
604
667
|
// Pull provider out of the just-written install-config.yaml so the
|
|
@@ -628,24 +691,54 @@ class SetupManager {
|
|
|
628
691
|
});
|
|
629
692
|
return;
|
|
630
693
|
}
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
694
|
+
// ─── Bundled-core fast path (offline framework) ──────────────────────────
|
|
695
|
+
// When the app ships specrails-core, materialize the versioned framework
|
|
696
|
+
// ONCE (instant/offline) and run the BUNDLED core `init` (offline framework
|
|
697
|
+
// assemble by symlink; openspec init is the only remaining network step) —
|
|
698
|
+
// NO `npx specrails-core` round-trip. When NO bundled core is present we fall
|
|
699
|
+
// through to the legacy npx probe + spawn, byte-identical to today.
|
|
700
|
+
const useBundledCore = (0, bundled_core_1.getBundledCoreCli)() !== null;
|
|
701
|
+
if (useBundledCore) {
|
|
702
|
+
// Materialize the framework for the selected provider (idempotent). The
|
|
703
|
+
// subsequent bundled `init` finds it already present (ensureFramework
|
|
704
|
+
// skips) and just assembles the workspace by symlink. Best-effort: a
|
|
705
|
+
// materialize failure is surfaced but the bundled init also re-runs
|
|
706
|
+
// ensureFramework, so it self-heals.
|
|
707
|
+
const provider = this._projectProviders.get(projectId) ?? 'claude';
|
|
708
|
+
try {
|
|
709
|
+
const fm = new framework_manager_1.FrameworkManager({
|
|
710
|
+
broadcast: (msg) => this._broadcast(msg),
|
|
711
|
+
});
|
|
712
|
+
const mat = fm.materialize((0, bundled_core_1.getBundledCoreVersion)() ?? undefined, [provider]);
|
|
713
|
+
if (mat.ran && mat.errors.length > 0) {
|
|
714
|
+
console.warn(`[SetupManager] framework materialize had errors: ${mat.errors.map((e) => `${e.provider}: ${e.message}`).join('; ')}`);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
catch (err) {
|
|
718
|
+
console.warn(`[SetupManager] framework materialize threw (non-fatal): ${err.message}`);
|
|
719
|
+
}
|
|
639
720
|
}
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
721
|
+
else {
|
|
722
|
+
// Legacy path: probe the npx-resolved core runtime (network) before spawn.
|
|
723
|
+
const probe = probeCoreRuntimeVersion(projectPath);
|
|
724
|
+
if (!probe.ok) {
|
|
725
|
+
this._broadcast({
|
|
726
|
+
type: 'setup_error',
|
|
727
|
+
projectId,
|
|
728
|
+
error: `Failed to verify specrails-core runtime before install: ${probe.error ?? 'unknown error'}`,
|
|
729
|
+
});
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
console.log(`[SetupManager] core runtime probe: ${probe.bin} -> ${probe.version}`);
|
|
733
|
+
const probeCmp = compareSemver(probe.version, MIN_NODE_NATIVE_CORE_VERSION);
|
|
734
|
+
if (probeCmp !== null && probeCmp < 0) {
|
|
735
|
+
this._broadcast({
|
|
736
|
+
type: 'setup_error',
|
|
737
|
+
projectId,
|
|
738
|
+
error: `Resolved specrails-core@${probe.version} is legacy; expected Node-native >= ${MIN_NODE_NATIVE_CORE_VERSION}.`,
|
|
739
|
+
});
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
649
742
|
}
|
|
650
743
|
let spawnConfigPath = null;
|
|
651
744
|
if (hasConfig) {
|
|
@@ -659,7 +752,10 @@ class SetupManager {
|
|
|
659
752
|
const initArgs = hasConfig
|
|
660
753
|
? ['--yes', '--from-config', spawnConfigPath ?? configPath]
|
|
661
754
|
: ['--yes', '--root-dir', projectPath];
|
|
662
|
-
|
|
755
|
+
// Bundled core (offline, node <cli> init) when available, else legacy npx.
|
|
756
|
+
const child = useBundledCore
|
|
757
|
+
? spawnBundledCoreInit(initArgs, projectPath)
|
|
758
|
+
: spawnCoreInit(initArgs, projectPath);
|
|
663
759
|
this._installProcesses.set(projectId, child);
|
|
664
760
|
this._installLogBuffer.set(projectId, []);
|
|
665
761
|
// spawnCoreInit uses shell:false on POSIX, so a spawn failure emits 'error'
|
|
@@ -738,7 +834,7 @@ class SetupManager {
|
|
|
738
834
|
this._broadcast({
|
|
739
835
|
type: 'setup_error',
|
|
740
836
|
projectId,
|
|
741
|
-
error: formatBufferedInstallError(
|
|
837
|
+
error: formatBufferedInstallError(`${useBundledCore ? 'bundled specrails-core' : 'npx specrails-core'} exited with code ${code ?? 'unknown'}`, logBuffer),
|
|
742
838
|
});
|
|
743
839
|
}
|
|
744
840
|
});
|
|
@@ -769,7 +865,12 @@ class SetupManager {
|
|
|
769
865
|
catch (err) {
|
|
770
866
|
console.warn(`[SetupManager] Failed to pre-create enrich directories: ${err}`);
|
|
771
867
|
}
|
|
772
|
-
|
|
868
|
+
// Relocate-artifacts: the install config lives in the per-project HOME dir.
|
|
869
|
+
// The slug was allocated at addProject and mirrored into the shared registry
|
|
870
|
+
// before/at install time, so resolve it from there (legacy full-flow path —
|
|
871
|
+
// not exercised by the app's quick flow).
|
|
872
|
+
const enrichSlug = (0, artifact_registry_1.resolveArtifacts)(projectPath).entry?.slug;
|
|
873
|
+
const configPath = (0, install_config_path_1.installConfigPath)({ slug: enrichSlug, path: projectPath });
|
|
773
874
|
const hasConfig = (0, fs_1.existsSync)(configPath);
|
|
774
875
|
const enrichCmd = hasConfig ? '/specrails:enrich --from-config' : '/specrails:enrich';
|
|
775
876
|
this._spawnSetupWithAdapter(projectId, projectPath, {
|
|
@@ -1148,16 +1249,17 @@ class SetupManager {
|
|
|
1148
1249
|
getInstallTier(projectId) {
|
|
1149
1250
|
return this._projectTiers.get(projectId);
|
|
1150
1251
|
}
|
|
1151
|
-
getSummary(
|
|
1152
|
-
const config = readInstallConfig(
|
|
1252
|
+
getSummary(project) {
|
|
1253
|
+
const config = readInstallConfig(project);
|
|
1153
1254
|
const tier = config?.tier ?? 'quick';
|
|
1154
1255
|
// Provider is authoritative from install-config.yaml when present; we
|
|
1155
1256
|
// do NOT fall back to filesystem heuristics because both `.codex/` and
|
|
1156
1257
|
// `.claude/` can legitimately coexist (e.g. a project that's been
|
|
1157
1258
|
// re-init'd) and a generic `existsSync` probe would mis-route.
|
|
1259
|
+
// Relocate-artifacts: the config lives in the per-project HOME dir.
|
|
1158
1260
|
let provider = 'claude';
|
|
1159
1261
|
try {
|
|
1160
|
-
const text = (0, fs_1.readFileSync)((0,
|
|
1262
|
+
const text = (0, fs_1.readFileSync)((0, install_config_path_1.installConfigPath)(project), 'utf-8');
|
|
1161
1263
|
const m = text.match(/^provider:\s*(\w+)/m);
|
|
1162
1264
|
if (m && m[1] && (0, providers_1.hasAdapter)(m[1]))
|
|
1163
1265
|
provider = m[1];
|
|
@@ -1165,7 +1267,7 @@ class SetupManager {
|
|
|
1165
1267
|
catch {
|
|
1166
1268
|
// Missing install-config — stay on claude default.
|
|
1167
1269
|
}
|
|
1168
|
-
return computeSummary(
|
|
1270
|
+
return computeSummary(project.path, tier, provider);
|
|
1169
1271
|
}
|
|
1170
1272
|
}
|
|
1171
1273
|
exports.SetupManager = SetupManager;
|