specrails-desktop 2.57.0 → 2.58.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 +1 -1
- package/cli/dist/args.js +10 -4
- package/cli/dist/help.js +1 -1
- package/client/dist/assets/{ActivityFeedPage-DdMNmVBk.js → ActivityFeedPage-BQGppIR0.js} +1 -1
- package/client/dist/assets/{AgentBrowserCapture-CeerUf--.js → AgentBrowserCapture-PI-cxyxi.js} +1 -1
- package/client/dist/assets/{AgentModeAnalyticsPane-BjGb-8rh.js → AgentModeAnalyticsPane-mQsOqdXx.js} +2 -2
- package/client/dist/assets/{AgentModeCodePane-8XRkQEo_.js → AgentModeCodePane-BL-vpmlY.js} +2 -2
- package/client/dist/assets/{AgentModeJobsPane-BN3dgJOA.js → AgentModeJobsPane-DR_06oqH.js} +1 -1
- package/client/dist/assets/AgentsPage-BKIKjm_1.js +118 -0
- package/client/dist/assets/{AnalyticsPage-CyfhYiBn.js → AnalyticsPage-069npRVo.js} +1 -1
- package/client/dist/assets/{CodePage-C2Nf-Q5W.js → CodePage-Alitorrv.js} +2 -2
- package/client/dist/assets/DesktopAnalyticsPage-qtWAPM47.js +1 -0
- package/client/dist/assets/{DocsDialog-Br3bczCp.js → DocsDialog-DwL6rgcT.js} +1 -1
- package/client/dist/assets/{DocsPage-BcDr2cBm.js → DocsPage-BA1hiJZ-.js} +1 -1
- package/client/dist/assets/{ExportDropdown-BVGKEgkd.js → ExportDropdown-BcMpvV89.js} +1 -1
- package/client/dist/assets/InteractiveJobComposer-B3H1YHr6.js +19 -0
- package/client/dist/assets/JobDetailModal-XZQ9eAAy.js +1 -0
- package/client/dist/assets/{JobDetailPage-vBbNeRuz.js → JobDetailPage-B-SIaq_q.js} +1 -1
- package/client/dist/assets/{JobsPage-CJCtPhIz.js → JobsPage-iQXAv2mx.js} +1 -1
- package/client/dist/assets/LoopBuilderPage-B_gbFAHn.js +1 -0
- package/client/dist/assets/LoopPreviewModal-je5pSCf-.js +1 -0
- package/client/dist/assets/LoopsPage-BZwcY5cu.js +2 -0
- package/client/dist/assets/{MinimizedChatsContext-D3QzgV0B.js → MinimizedChatsContext-CNx_0bZu.js} +1 -1
- package/client/dist/assets/{PluginsPage-DOpTV75p.js → PluginsPage-DFocnoHX.js} +2 -2
- package/client/dist/assets/{ProjectSettingsDialog-Dkwrn0z1.js → ProjectSettingsDialog-Bh3S6EBB.js} +1 -1
- package/client/dist/assets/{RepositoryDeliveries-Du2UCf1a.js → RepositoryDeliveries-BiwJuS8r.js} +1 -1
- package/client/dist/assets/{RepositoryScopeSelector-hPGZNRBm.js → RepositoryScopeSelector-C2mvWBQ6.js} +1 -1
- package/client/dist/assets/{ReviewPacketPage-DcOq-UTA.js → ReviewPacketPage-CrAf-WDy.js} +2 -2
- package/client/dist/assets/TemplatePreviewModal-C7DordpN.js +1 -0
- package/client/dist/assets/{TicketDetailModalContext-DAK-NoK6.js → TicketDetailModalContext-B-DySpKL.js} +1 -1
- package/client/dist/assets/{Trans-DEbgkuAY.js → Trans-D3AfDylf.js} +1 -1
- package/client/dist/assets/agentRuntime-BeUb3g9g.js +1 -0
- package/client/dist/assets/agentRuntime-Bg9tbmqm.js +1 -0
- package/client/dist/assets/agentRuntime-D-GJVGkj.js +1 -0
- package/client/dist/assets/agentRuntime-D2__CVI_.js +1 -0
- package/client/dist/assets/agentRuntime-D35dGVOP.js +1 -0
- package/client/dist/assets/agentRuntime-DGz_IqKW.js +1 -0
- package/client/dist/assets/agentRuntime-O1liDt6G.js +1 -0
- package/client/dist/assets/agentRuntime-hInH9Fzj.js +1 -0
- package/client/dist/assets/agentstudio-B2CTtQgZ.js +1 -0
- package/client/dist/assets/agentstudio-BWAKeGgB.js +1 -0
- package/client/dist/assets/{agentstudio-DR7doNBV.js → agentstudio-Cja8Sjcp.js} +1 -1
- package/client/dist/assets/agentstudio-DtfPcr99.js +1 -0
- package/client/dist/assets/agentstudio-QdAJIDef.js +1 -0
- package/client/dist/assets/agentstudio-X70BKf6u.js +1 -0
- package/client/dist/assets/agentstudio-fLXpRGIi.js +1 -0
- package/client/dist/assets/agentstudio-qXKqVnWS.js +1 -0
- package/client/dist/assets/{analytics-jzCkOec3.js → analytics-B1DIQd4m.js} +1 -1
- package/client/dist/assets/{analytics-CoEzWPx4.js → analytics-BAIZ9vE_.js} +1 -1
- package/client/dist/assets/{analytics-DjLlE7jS.js → analytics-BTqgmgBd.js} +1 -1
- package/client/dist/assets/analytics-BWynXPEm.js +1 -0
- package/client/dist/assets/{analytics-nZIjjtCc.js → analytics-DGcUsQ44.js} +1 -1
- package/client/dist/assets/{analytics-BNgZ1Nj7.js → analytics-GnSnpJ4W.js} +1 -1
- package/client/dist/assets/{analytics-DFh5lSOl.js → analytics-TWgYaoO0.js} +1 -1
- package/client/dist/assets/{analytics-DZp-DS2U.js → analytics-kJy6Tuko.js} +1 -1
- package/client/dist/assets/builder-C0FyvM83.js +1 -0
- package/client/dist/assets/builder-C1neIYGR.js +1 -0
- package/client/dist/assets/builder-CvnFm0im.js +1 -0
- package/client/dist/assets/builder-CzW5ovyI.js +1 -0
- package/client/dist/assets/builder-D06L7iPy.js +1 -0
- package/client/dist/assets/builder-DGETbtZj.js +1 -0
- package/client/dist/assets/builder-DXEJUlC0.js +1 -0
- package/client/dist/assets/builder-DyQperXU.js +1 -0
- package/client/dist/assets/common-BCK7c9Pv.js +1 -0
- package/client/dist/assets/common-C0BOdeNi.js +1 -0
- package/client/dist/assets/common-C4Yvvhp6.js +1 -0
- package/client/dist/assets/{common-BrlwEnL7.js → common-DmsclwjP.js} +1 -1
- package/client/dist/assets/common-DpLWwgI1.js +1 -0
- package/client/dist/assets/common-OCvl3c8t.js +1 -0
- package/client/dist/assets/common-c0aujEZC.js +1 -0
- package/client/dist/assets/common-od8WThlf.js +1 -0
- package/client/dist/assets/dashboard-B1ndRHuC.js +1 -0
- package/client/dist/assets/dashboard-BX7KXiqA.js +1 -0
- package/client/dist/assets/dashboard-B_BSNxcN.js +1 -0
- package/client/dist/assets/dashboard-CCeFN7-V.js +1 -0
- package/client/dist/assets/dashboard-CRl0WtFJ.js +1 -0
- package/client/dist/assets/dashboard-XGdQ5mjw.js +1 -0
- package/client/dist/assets/dashboard-_oltA6Jd.js +1 -0
- package/client/dist/assets/dashboard-bAuf5KVk.js +1 -0
- package/client/dist/assets/{formatDistanceToNow-Cc2u5FwQ.js → formatDistanceToNow-aBss1Nai.js} +1 -1
- package/client/dist/assets/{getTimezoneOffsetInMilliseconds-BHm2U-w1.js → getTimezoneOffsetInMilliseconds-DOGj1Mfz.js} +1 -1
- package/client/dist/assets/index-BS7KCWkV.css +2 -0
- package/client/dist/assets/{index-Cxb288Rj.js → index-C4Mv6tV6.js} +32 -32
- package/client/dist/assets/{jira-api-DrGxiD9p.js → jira-api-BYbEPlkr.js} +1 -1
- package/client/dist/assets/{jobs-DSp9DoD_.js → jobs-6_-r75yC.js} +1 -1
- package/client/dist/assets/{jobs-C6qmvUAi.js → jobs-B1yqyId5.js} +1 -1
- package/client/dist/assets/{jobs-DoFaEl9g.js → jobs-BFuV7ttH.js} +1 -1
- package/client/dist/assets/{jobs-DTQO08nJ.js → jobs-BKa8LuwA.js} +1 -1
- package/client/dist/assets/{jobs-DrF8Do8s.js → jobs-BKiK-ZWh.js} +1 -1
- package/client/dist/assets/{jobs-DLHdDkHc.js → jobs-DmKV7b7d.js} +1 -1
- package/client/dist/assets/{jobs-C9j2PuwW.js → jobs-KG4Sn12X.js} +1 -1
- package/client/dist/assets/{jobs-Dpw6AEOP.js → jobs-pPv4KWjb.js} +1 -1
- package/client/dist/assets/loop-layout-BF8xMiNv.js +7 -0
- package/client/dist/assets/loops-B4_D-nbg.js +1 -0
- package/client/dist/assets/loops-BJDY9FTh.js +1 -0
- package/client/dist/assets/loops-BO3V8244.js +1 -0
- package/client/dist/assets/loops-C-hZK8pf.js +1 -0
- package/client/dist/assets/loops-CDOWZ0ci.js +1 -0
- package/client/dist/assets/loops-Dv849ckF.js +1 -0
- package/client/dist/assets/loops-Q9LIgGkq.js +1 -0
- package/client/dist/assets/loops-eUnCfLjC.js +1 -0
- package/client/dist/assets/{project-repositories-9Wzs0coQ.js → project-repositories-CSnfzUpg.js} +1 -1
- package/client/dist/assets/settings-3o4Ntzi5.js +1 -0
- package/client/dist/assets/settings-B97jlkwD.js +1 -0
- package/client/dist/assets/settings-BTaT_bsS.js +1 -0
- package/client/dist/assets/settings-CT8w7WiC.js +1 -0
- package/client/dist/assets/settings-Cvr8_tvh.js +1 -0
- package/client/dist/assets/settings-Dn1qgAC8.js +1 -0
- package/client/dist/assets/settings-EShDdg4J.js +1 -0
- package/client/dist/assets/settings-tfLhQZRN.js +1 -0
- package/client/dist/assets/{setup-Dwzt5nO1.js → setup-CBMu3tqH.js} +1 -1
- package/client/dist/assets/{setup-B5FHuxvB.js → setup-CPh-e6J2.js} +1 -1
- package/client/dist/assets/{setup-DpqzaEEA.js → setup-CRKLp_ke.js} +1 -1
- package/client/dist/assets/{setup-CROyqD5N.js → setup-Cy302v4J.js} +1 -1
- package/client/dist/assets/{setup-C9Fy9PKQ.js → setup-D681vTPf.js} +1 -1
- package/client/dist/assets/{setup-BihzXadx.js → setup-DYK0VBkE.js} +1 -1
- package/client/dist/assets/{setup-BtC6Hg4K.js → setup-KAuiIiVs.js} +1 -1
- package/client/dist/assets/{setup-zLEDnG7N.js → setup-OGX-oCfI.js} +1 -1
- package/client/dist/assets/{spending-T5GM7wpn.js → spending-HBSk-6jv.js} +1 -1
- package/client/dist/assets/{useDesktop-QogiN0zR.js → useDesktop-0unHDBJJ.js} +1 -1
- package/client/dist/assets/useRuntimeRuns-BN1rhyEm.js +1 -0
- package/client/dist/assets/{useSharedWebSocket-Dz-HI6De.js → useSharedWebSocket-BZMrX4jr.js} +2 -2
- package/client/dist/index.html +25 -25
- package/docs/agent-live-steering.md +16 -0
- package/docs/cli.md +7 -7
- package/docs/codex.md +2 -2
- package/docs/customizing.md +1 -1
- package/docs/gemini.md +1 -1
- package/docs/getting-started.md +1 -1
- package/docs/guide/de/integrations/7-local-engines.md +1 -1
- package/docs/guide/de/pipeline/1-rails-and-jobs.md +8 -6
- package/docs/guide/de/pipeline/2-the-job-detail-view.md +11 -2
- package/docs/guide/de/pipeline/4-picking-an-engine-per-rail.md +2 -2
- package/docs/guide/de/pipeline/5-the-loop-builder.md +24 -5
- package/docs/guide/en/integrations/7-local-engines.md +1 -1
- package/docs/guide/en/pipeline/1-rails-and-jobs.md +8 -6
- package/docs/guide/en/pipeline/2-the-job-detail-view.md +11 -2
- package/docs/guide/en/pipeline/4-picking-an-engine-per-rail.md +2 -2
- package/docs/guide/en/pipeline/5-the-loop-builder.md +34 -5
- package/docs/guide/en/settings/3-pipeline-telemetry-and-diagnostics.md +1 -1
- package/docs/guide/es/integrations/7-local-engines.md +1 -1
- package/docs/guide/es/pipeline/1-rails-and-jobs.md +8 -6
- package/docs/guide/es/pipeline/2-the-job-detail-view.md +11 -2
- package/docs/guide/es/pipeline/4-picking-an-engine-per-rail.md +2 -2
- package/docs/guide/es/pipeline/5-the-loop-builder.md +30 -5
- package/docs/guide/es/settings/3-pipeline-telemetry-and-diagnostics.md +1 -2
- package/docs/guide/fr/integrations/7-local-engines.md +1 -1
- package/docs/guide/fr/pipeline/1-rails-and-jobs.md +8 -6
- package/docs/guide/fr/pipeline/2-the-job-detail-view.md +11 -2
- package/docs/guide/fr/pipeline/4-picking-an-engine-per-rail.md +2 -2
- package/docs/guide/fr/pipeline/5-the-loop-builder.md +24 -5
- package/docs/guide/it/integrations/7-local-engines.md +1 -1
- package/docs/guide/it/pipeline/1-rails-and-jobs.md +8 -6
- package/docs/guide/it/pipeline/2-the-job-detail-view.md +11 -2
- package/docs/guide/it/pipeline/4-picking-an-engine-per-rail.md +2 -2
- package/docs/guide/it/pipeline/5-the-loop-builder.md +24 -5
- package/docs/guide/ja/integrations/7-local-engines.md +1 -1
- package/docs/guide/ja/pipeline/1-rails-and-jobs.md +8 -6
- package/docs/guide/ja/pipeline/2-the-job-detail-view.md +11 -2
- package/docs/guide/ja/pipeline/4-picking-an-engine-per-rail.md +2 -2
- package/docs/guide/ja/pipeline/5-the-loop-builder.md +24 -5
- package/docs/guide/pt/integrations/7-local-engines.md +1 -1
- package/docs/guide/pt/pipeline/1-rails-and-jobs.md +8 -6
- package/docs/guide/pt/pipeline/2-the-job-detail-view.md +11 -2
- package/docs/guide/pt/pipeline/4-picking-an-engine-per-rail.md +2 -2
- package/docs/guide/pt/pipeline/5-the-loop-builder.md +24 -5
- package/docs/guide/zh/integrations/7-local-engines.md +1 -1
- package/docs/guide/zh/pipeline/1-rails-and-jobs.md +8 -6
- package/docs/guide/zh/pipeline/2-the-job-detail-view.md +11 -2
- package/docs/guide/zh/pipeline/4-picking-an-engine-per-rail.md +2 -2
- package/docs/guide/zh/pipeline/5-the-loop-builder.md +24 -5
- package/docs/internals/api-reference.md +2 -2
- package/docs/internals/ci-performance.md +71 -0
- package/docs/internals/companion-rails-as-loops-contract.md +4 -3
- package/docs/internals/configuration.md +3 -3
- package/docs/internals/core-runtime-updates.md +3 -2
- package/docs/internals/interactive-jobs.md +2 -2
- package/docs/internals/loop-step-log-explorer.md +42 -0
- package/docs/internals/mission-rail-cards.md +1 -1
- package/docs/internals/operations-runbook.md +22 -0
- package/docs/internals/programmatic-agent-runtime.md +130 -7
- package/docs/internals/project-builder.md +35 -16
- package/docs/internals/source-map.md +48 -2
- package/docs/kimi.md +2 -2
- package/docs/local-providers.md +1 -1
- package/docs/running-pipelines.md +26 -18
- package/docs/tracking-cost.md +10 -1
- package/package.json +2 -1
- package/server/dist/core-compat.js +8 -3
- package/server/dist/core-execution.js +49 -3
- package/server/dist/db/migrations.js +101 -0
- package/server/dist/desktop-db.js +28 -0
- package/server/dist/desktop-router.js +2 -0
- package/server/dist/mcp/guide.js +6 -3
- package/server/dist/mcp/tools/jobs.js +14 -1
- package/server/dist/mcp/tools/loops.js +17 -8
- package/server/dist/mcp/tools/rails.js +3 -3
- package/server/dist/modules/agent-runtime/runtime/agent-runtime-bridge.js +313 -9
- package/server/dist/modules/agent-runtime/runtime/agent-runtime-controls-router.js +34 -1
- package/server/dist/modules/agent-runtime/runtime/agent-runtime-controls.js +203 -21
- package/server/dist/modules/agent-runtime/runtime/agent-runtime-effective-config.js +46 -1
- package/server/dist/modules/agent-runtime/runtime/agent-runtime-engines.js +10 -0
- package/server/dist/modules/agent-runtime/runtime/agent-runtime-loader.js +68 -3
- package/server/dist/modules/agent-runtime/runtime/agent-runtime-metrics.js +16 -8
- package/server/dist/modules/agent-runtime/runtime/agent-runtime-package-gc.js +81 -0
- package/server/dist/modules/agent-runtime/runtime/agent-runtime-package-lock.js +37 -0
- package/server/dist/modules/agent-runtime/runtime/agent-runtime-package.js +30 -24
- package/server/dist/modules/agent-runtime/runtime/agent-runtime-retention-host.js +87 -0
- package/server/dist/modules/agent-runtime/runtime/agent-runtime-retention-quarantine.js +231 -0
- package/server/dist/modules/agent-runtime/runtime/agent-runtime-retention-records.js +32 -0
- package/server/dist/modules/agent-runtime/runtime/agent-runtime-retention.js +102 -0
- package/server/dist/modules/agent-runtime/runtime/agent-runtime-settings-router.js +12 -7
- package/server/dist/modules/agent-runtime/runtime/agent-runtime-settings.js +57 -13
- package/server/dist/modules/agents/runtime/agent-catalog.js +32 -0
- package/server/dist/modules/agents/runtime/agent-refine-manager.js +12 -0
- package/server/dist/modules/agents/runtime/agent-role-descriptor.js +64 -0
- package/server/dist/modules/agents/runtime/profiles-router.js +26 -34
- package/server/dist/modules/builder/runtime/milestone-chain.js +121 -73
- package/server/dist/modules/delivery/runtime/definition-fork.js +158 -0
- package/server/dist/modules/delivery/runtime/delivery-evidence.js +56 -10
- package/server/dist/modules/delivery/runtime/isolated-settlement-reconstruction.js +99 -0
- package/server/dist/modules/delivery/runtime/isolated-settlement-store.js +53 -0
- package/server/dist/modules/delivery/runtime/multi-repo-execution-store.js +3 -1
- package/server/dist/modules/delivery/runtime/rail-isolated-launch.js +388 -203
- package/server/dist/modules/delivery/runtime/rail-launch-parser.js +3 -2
- package/server/dist/modules/delivery/runtime/rail-pr-store.js +6 -1
- package/server/dist/modules/delivery/runtime/rails-router.js +66 -10
- package/server/dist/modules/execution/runtime/queue-manager.js +11 -2
- package/server/dist/modules/loops/runtime/builtin-loops.js +70 -0
- package/server/dist/modules/loops/runtime/definition-cancellation.js +31 -0
- package/server/dist/modules/loops/runtime/legacy-launch-telemetry.js +78 -0
- package/server/dist/modules/loops/runtime/loop-command-catalog.js +13 -12
- package/server/dist/modules/loops/runtime/loop-compat.js +219 -0
- package/server/dist/modules/loops/runtime/loop-core-factory.js +42 -0
- package/server/dist/modules/loops/runtime/loop-definition-controls.js +48 -0
- package/server/dist/modules/loops/runtime/loop-definition-events.js +145 -0
- package/server/dist/modules/loops/runtime/loop-definition-recovery.js +106 -0
- package/server/dist/modules/loops/runtime/loop-definition-run.js +36 -0
- package/server/dist/modules/loops/runtime/loop-definition.js +246 -0
- package/server/dist/modules/loops/runtime/loop-effect.js +10 -1
- package/server/dist/modules/loops/runtime/loop-executors.js +63 -3
- package/server/dist/modules/loops/runtime/loop-factory.js +35 -15
- package/server/dist/modules/loops/runtime/loop-graph.js +84 -8
- package/server/dist/modules/loops/runtime/loop-migration.js +36 -0
- package/server/dist/modules/loops/runtime/loop-preview.js +10 -0
- package/server/dist/modules/loops/runtime/loop-run-manager.js +206 -13
- package/server/dist/modules/loops/runtime/loop-runs-store.js +271 -7
- package/server/dist/modules/loops/runtime/loop-templates.js +205 -45
- package/server/dist/modules/loops/runtime/loops-router.js +246 -29
- package/server/dist/modules/loops/runtime/loops-store.js +140 -20
- package/server/dist/modules/missions/runtime/agent-operator-prompt.js +14 -11
- package/server/dist/modules/specs/runtime/spec-addenda.js +28 -0
- package/server/dist/project-registry.js +87 -51
- package/server/dist/project-router-jobs.js +17 -6
- package/server/dist/project-router-loop-runs.js +200 -0
- package/server/dist/project-router-spending.js +17 -0
- package/server/dist/project-router.js +8 -6
- package/server/dist/schemas/agent-runtime.schema.json +93 -1
- package/server/dist/schemas/workflow-definition.schema.json +312 -0
- package/server/dist/vitest-setup.js +22 -0
- package/server/dist/worktree-overlay.js +1 -1
- package/client/dist/assets/AgentsPage-DfHWgP_x.js +0 -87
- package/client/dist/assets/DesktopAnalyticsPage-tc0p4jJW.js +0 -1
- package/client/dist/assets/InteractiveJobComposer-DFPpdg2D.js +0 -19
- package/client/dist/assets/JobDetailModal-BdOv-l-M.js +0 -1
- package/client/dist/assets/LoopBuilderPage-BemvUOxy.js +0 -7
- package/client/dist/assets/LoopPreviewModal-C6hNAeYN.js +0 -1
- package/client/dist/assets/LoopsPage-BZr0quvh.js +0 -1
- package/client/dist/assets/TemplatePreviewModal-Cejcquah.js +0 -1
- package/client/dist/assets/agentRuntime-BUimiX__.js +0 -1
- package/client/dist/assets/agentRuntime-BcN97sAB.js +0 -1
- package/client/dist/assets/agentRuntime-Ckyg-ahT.js +0 -1
- package/client/dist/assets/agentRuntime-DJCwyX4t.js +0 -1
- package/client/dist/assets/agentRuntime-DVzPJOl0.js +0 -1
- package/client/dist/assets/agentRuntime-V9AZTG3c.js +0 -1
- package/client/dist/assets/agentRuntime-bAAjw1vw.js +0 -1
- package/client/dist/assets/agentRuntime-v4qKAYH-.js +0 -1
- package/client/dist/assets/agentstudio-BNUth0uj.js +0 -1
- package/client/dist/assets/agentstudio-BNdB5hkr.js +0 -1
- package/client/dist/assets/agentstudio-C7-bt8FO.js +0 -1
- package/client/dist/assets/agentstudio-C8uNchpV.js +0 -1
- package/client/dist/assets/agentstudio-CKabyMlg.js +0 -1
- package/client/dist/assets/agentstudio-CN2pokQu.js +0 -1
- package/client/dist/assets/agentstudio-y2dVDXeM.js +0 -1
- package/client/dist/assets/analytics-BlypLLR6.js +0 -1
- package/client/dist/assets/builder-765NeYTp.js +0 -1
- package/client/dist/assets/builder-8MG3V6ea.js +0 -1
- package/client/dist/assets/builder-B-1UBs5K.js +0 -1
- package/client/dist/assets/builder-BTiGnPT0.js +0 -1
- package/client/dist/assets/builder-Cx9Rz-7u.js +0 -1
- package/client/dist/assets/builder-DKFMELBl.js +0 -1
- package/client/dist/assets/builder-OcmKrC0h.js +0 -1
- package/client/dist/assets/builder-P9qVBXFZ.js +0 -1
- package/client/dist/assets/common-B5AZNYMH.js +0 -1
- package/client/dist/assets/common-BFZ0c_vN.js +0 -1
- package/client/dist/assets/common-BttQx3z4.js +0 -1
- package/client/dist/assets/common-CM65k_-e.js +0 -1
- package/client/dist/assets/common-D4mvFFlu.js +0 -1
- package/client/dist/assets/common-TZ9Ny96S.js +0 -1
- package/client/dist/assets/common-nFpSF4GC.js +0 -1
- package/client/dist/assets/dashboard-B9uWNGO2.js +0 -1
- package/client/dist/assets/dashboard-BIwmO9wc.js +0 -1
- package/client/dist/assets/dashboard-BV4vOZkR.js +0 -1
- package/client/dist/assets/dashboard-Bf6MPVsM.js +0 -1
- package/client/dist/assets/dashboard-C8klWVvZ.js +0 -1
- package/client/dist/assets/dashboard-D1ORhPF0.js +0 -1
- package/client/dist/assets/dashboard-DSubvs9x.js +0 -1
- package/client/dist/assets/dashboard-px_vlRx_.js +0 -1
- package/client/dist/assets/index-C51x61Bz.css +0 -2
- package/client/dist/assets/loops-B7YpGizO.js +0 -1
- package/client/dist/assets/loops-BomD2J-h.js +0 -1
- package/client/dist/assets/loops-BrQHSQw3.js +0 -1
- package/client/dist/assets/loops-BvBwvTPA.js +0 -1
- package/client/dist/assets/loops-CODk6Slz.js +0 -1
- package/client/dist/assets/loops-DC6k-Ym-.js +0 -1
- package/client/dist/assets/loops-DSEg8-uY.js +0 -1
- package/client/dist/assets/loops-DhCoXD_v.js +0 -1
- package/client/dist/assets/settings-BsehKpty.js +0 -1
- package/client/dist/assets/settings-CQWob3SB.js +0 -1
- package/client/dist/assets/settings-CgCl0hzy.js +0 -1
- package/client/dist/assets/settings-DLRLd22L.js +0 -1
- package/client/dist/assets/settings-Dn5prAFl.js +0 -1
- package/client/dist/assets/settings-HZSu-K4l.js +0 -1
- package/client/dist/assets/settings-p4OVIyRh.js +0 -1
- package/client/dist/assets/settings-rddaldUp.js +0 -1
- package/client/dist/assets/useRuntimeRuns-Dqmz9fS9.js +0 -1
- package/docs/guide/de/pipeline/3-batch-implement-and-multi-feature.md +0 -84
- package/docs/guide/en/pipeline/3-batch-implement-and-multi-feature.md +0 -126
- package/docs/guide/es/pipeline/3-batch-implement-and-multi-feature.md +0 -114
- package/docs/guide/fr/pipeline/3-batch-implement-and-multi-feature.md +0 -84
- package/docs/guide/it/pipeline/3-batch-implement-and-multi-feature.md +0 -84
- package/docs/guide/ja/pipeline/3-batch-implement-and-multi-feature.md +0 -84
- package/docs/guide/pt/pipeline/3-batch-implement-and-multi-feature.md +0 -84
- package/docs/guide/zh/pipeline/3-batch-implement-and-multi-feature.md +0 -84
- package/server/dist/modules/loops/runtime/loop-templates-ported.js +0 -675
- /package/client/dist/assets/{LoopBuilderPage-DtxX27Jz.css → loop-layout-DtxX27Jz.css} +0 -0
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
// Server-durable milestone launch chain (premium-milestone-progress, D3/D4).
|
|
3
3
|
//
|
|
4
|
-
// "Launch Milestone N"
|
|
5
|
-
// (
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
4
|
+
// "Launch Milestone N": order the milestone's todo specs by their declared
|
|
5
|
+
// dependencies (prerequisites, then execution_order), put ONE spec on each
|
|
6
|
+
// rail (mode `implement`), launch the first through the ORDINARY rails launch
|
|
7
|
+
// route, and — when that spec's delivery settles — launch the next one STACKED
|
|
8
|
+
// on the delivered branch, so a greenfield walking skeleton accumulates without
|
|
9
|
+
// waiting for a merge. Launches are always sequential (the Parallel option was
|
|
10
|
+
// removed); auto-continue is on by default and a failure always pauses. The plan lives in SQLite (survives window close, app
|
|
9
11
|
// restart, machine sleep), advances from the delivery-settle chokepoint (the
|
|
10
12
|
// `rail.pr_state` broadcast tapped in the project's bound broadcast — the
|
|
11
13
|
// engine's `onLoopRunFinished` fires BEFORE the delivery row leaves
|
|
@@ -13,32 +15,63 @@
|
|
|
13
15
|
// pauses with a typed reason on any failure (never skips ahead), and is
|
|
14
16
|
// visible/controllable (resume / cancel) from every surface.
|
|
15
17
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
-
exports.MilestoneChainManager = exports.MAX_TICKETS_PER_CHAIN_CHUNK = void 0;
|
|
17
|
-
exports.isMilestoneChainEnabled = isMilestoneChainEnabled;
|
|
18
|
+
exports.MilestoneChainManager = exports.RAIL_LIMIT_REACHED = exports.MAX_TICKETS_PER_CHAIN_CHUNK = void 0;
|
|
18
19
|
exports.chunkTickets = chunkTickets;
|
|
20
|
+
exports.orderChainTickets = orderChainTickets;
|
|
19
21
|
exports.chainRailName = chainRailName;
|
|
22
|
+
exports.isBuilderRailName = isBuilderRailName;
|
|
20
23
|
const milestone_progress_1 = require("./milestone-progress");
|
|
21
24
|
const milestone_chain_store_1 = require("./milestone-chain-store");
|
|
22
25
|
const ids_1 = require("../../../ids");
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
function isMilestoneChainEnabled(env = process.env) {
|
|
26
|
-
const raw = env.SPECRAILS_MILESTONE_CHAIN;
|
|
27
|
-
if (raw === undefined || raw === '')
|
|
28
|
-
return true;
|
|
29
|
-
const normalized = raw.trim().toLowerCase();
|
|
30
|
-
return !(normalized === '0' || normalized === 'false' || normalized === 'off');
|
|
31
|
-
}
|
|
26
|
+
/** One spec per rail: every chunk of a milestone chain carries exactly one spec. */
|
|
27
|
+
exports.MAX_TICKETS_PER_CHAIN_CHUNK = 1;
|
|
32
28
|
function chunkTickets(ticketIds, size = exports.MAX_TICKETS_PER_CHAIN_CHUNK) {
|
|
33
29
|
const chunks = [];
|
|
34
30
|
for (let i = 0; i < ticketIds.length; i += size)
|
|
35
31
|
chunks.push(ticketIds.slice(i, i + size));
|
|
36
32
|
return chunks;
|
|
37
33
|
}
|
|
38
|
-
|
|
34
|
+
/**
|
|
35
|
+
* Launch order for a milestone's specs: a stable topological order over the
|
|
36
|
+
* specs' `prerequisites` (edges to specs outside the set — already done or in
|
|
37
|
+
* another milestone — are ignored), ties broken by `execution_order` (unset
|
|
38
|
+
* last) and then id. A dependency cycle cannot block the milestone: the
|
|
39
|
+
* remaining specs follow in tie order.
|
|
40
|
+
*/
|
|
41
|
+
function orderChainTickets(tickets) {
|
|
42
|
+
const byId = new Map(tickets.map((t) => [t.id, t]));
|
|
43
|
+
const rank = (a, b) => {
|
|
44
|
+
const ea = byId.get(a)?.executionOrder ?? Number.POSITIVE_INFINITY;
|
|
45
|
+
const eb = byId.get(b)?.executionOrder ?? Number.POSITIVE_INFINITY;
|
|
46
|
+
return ea !== eb ? (ea < eb ? -1 : 1) : a - b;
|
|
47
|
+
};
|
|
48
|
+
const pending = new Map();
|
|
49
|
+
for (const t of tickets) {
|
|
50
|
+
pending.set(t.id, new Set((t.prerequisites ?? []).filter((id) => id !== t.id && byId.has(id))));
|
|
51
|
+
}
|
|
52
|
+
const order = [];
|
|
53
|
+
while (pending.size > 0) {
|
|
54
|
+
const ready = [...pending.entries()].filter(([, deps]) => deps.size === 0).map(([id]) => id).sort(rank);
|
|
55
|
+
// Cycle: take the best-ranked remaining spec so the chain still progresses.
|
|
56
|
+
const next = ready[0] ?? [...pending.keys()].sort(rank)[0];
|
|
57
|
+
order.push(next);
|
|
58
|
+
pending.delete(next);
|
|
59
|
+
for (const deps of pending.values())
|
|
60
|
+
deps.delete(next);
|
|
61
|
+
}
|
|
62
|
+
return order;
|
|
63
|
+
}
|
|
64
|
+
/** Builder rails are named after their spec: `M1 · #12`. */
|
|
65
|
+
function chainRailName(n, ticketIds) {
|
|
39
66
|
const label = (0, milestone_progress_1.milestoneLabelFor)(n);
|
|
40
|
-
return
|
|
67
|
+
return ticketIds.length ? `${label} · ${ticketIds.map((id) => `#${id}`).join(' ')}` : label;
|
|
68
|
+
}
|
|
69
|
+
/** A rail name the Project Builder owns (`M<n>` or `M<n> · …`). */
|
|
70
|
+
function isBuilderRailName(name) {
|
|
71
|
+
return typeof name === 'string' && /^M\d+(?: · .+)?$/.test(name);
|
|
41
72
|
}
|
|
73
|
+
/** Pause reason when every rail slot is taken by undecided work. */
|
|
74
|
+
exports.RAIL_LIMIT_REACHED = 'rail_limit_reached';
|
|
42
75
|
const SUCCESS_DECISIONS = new Set(['on_review', 'no_changes', 'pr_draft', 'pr_ready', 'completed', 'merged']);
|
|
43
76
|
class MilestoneChainManager {
|
|
44
77
|
db;
|
|
@@ -52,9 +85,6 @@ class MilestoneChainManager {
|
|
|
52
85
|
now() {
|
|
53
86
|
return this.io.now?.() ?? Date.now();
|
|
54
87
|
}
|
|
55
|
-
enabled() {
|
|
56
|
-
return this.io.enabled?.() ?? isMilestoneChainEnabled();
|
|
57
|
-
}
|
|
58
88
|
/** Non-terminal chains. */
|
|
59
89
|
listActive() {
|
|
60
90
|
return (0, milestone_chain_store_1.listActiveChains)(this.db).map(milestone_chain_store_1.toChainSnapshot);
|
|
@@ -88,34 +118,37 @@ class MilestoneChainManager {
|
|
|
88
118
|
}
|
|
89
119
|
todoTicketIds(n) {
|
|
90
120
|
const label = (0, milestone_progress_1.milestoneLabelFor)(n);
|
|
91
|
-
return this.io.readTickets()
|
|
92
|
-
.filter((t) => t.status === 'todo' && Array.isArray(t.labels) && t.labels.includes(label))
|
|
93
|
-
.map((t) => t.id)
|
|
94
|
-
.sort((a, b) => a - b);
|
|
121
|
+
return orderChainTickets(this.io.readTickets()
|
|
122
|
+
.filter((t) => t.status === 'todo' && Array.isArray(t.labels) && t.labels.includes(label)));
|
|
95
123
|
}
|
|
96
|
-
/**
|
|
97
|
-
*
|
|
98
|
-
|
|
124
|
+
/** Assign → launch ONE spec on a rail: the retried chunk's previous rail,
|
|
125
|
+
* else a free builder-owned rail (same name, this chain's, then any builder
|
|
126
|
+
* rail), else a fresh one. At the rail limit it fails `rail_limit_reached`. */
|
|
127
|
+
async launchChunk(n, chunks, chunkIndex, baseBranch, reuseRailIndex = null, baseDeliveryIds, chain) {
|
|
99
128
|
const ticketIds = chunks[chunkIndex];
|
|
100
129
|
let railIndex;
|
|
101
|
-
const name = chainRailName(n,
|
|
102
|
-
const
|
|
103
|
-
if (
|
|
104
|
-
railIndex =
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
130
|
+
const name = chainRailName(n, ticketIds);
|
|
131
|
+
const reused = reuseRailIndex ?? this.freeRailNamed(name) ?? this.freeBuilderRail(chain);
|
|
132
|
+
if (reused !== null) {
|
|
133
|
+
railIndex = reused;
|
|
134
|
+
if (this.railName(reused) !== name && this.io.renameRail) {
|
|
135
|
+
// Best effort: a stale label is cosmetic, never a reason to stop.
|
|
136
|
+
try {
|
|
137
|
+
await this.io.renameRail(reused, name);
|
|
138
|
+
}
|
|
139
|
+
catch { /* keep the old label */ }
|
|
140
|
+
}
|
|
108
141
|
}
|
|
109
142
|
else {
|
|
110
143
|
const rail = await this.io.createRail(name);
|
|
111
144
|
if (!rail.ok)
|
|
112
|
-
return rail;
|
|
145
|
+
return rail.error === exports.RAIL_LIMIT_REACHED ? { ...rail, status: 409 } : rail;
|
|
113
146
|
railIndex = rail.railIndex;
|
|
114
147
|
}
|
|
115
148
|
const assigned = await this.io.assignTickets(railIndex, ticketIds);
|
|
116
149
|
if (!assigned.ok)
|
|
117
150
|
return assigned;
|
|
118
|
-
const launched = await this.io.launch(railIndex, { mode: '
|
|
151
|
+
const launched = await this.io.launch(railIndex, { mode: 'implement', ...(baseDeliveryIds?.length ? { baseDeliveryIds } : baseBranch ? { baseBranch } : {}) });
|
|
119
152
|
if (!launched.ok)
|
|
120
153
|
return launched;
|
|
121
154
|
const delivery = this.io.activeDeliveryForRail(railIndex);
|
|
@@ -131,7 +164,39 @@ class MilestoneChainManager {
|
|
|
131
164
|
const index = this.io.findRailByName?.(name) ?? null;
|
|
132
165
|
if (index === null)
|
|
133
166
|
return null;
|
|
134
|
-
return this.
|
|
167
|
+
return this.railFree(index) ? index : null;
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
railName(railIndex) {
|
|
174
|
+
try {
|
|
175
|
+
return this.io.listRails?.().find((r) => r.railIndex === railIndex)?.name ?? null;
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
/** Nothing undecided, nothing running, and no other active chain mid-flight on it. */
|
|
182
|
+
railFree(railIndex) {
|
|
183
|
+
if (this.io.activeDeliveryForRail(railIndex))
|
|
184
|
+
return false;
|
|
185
|
+
if (this.io.railBusy?.(railIndex))
|
|
186
|
+
return false;
|
|
187
|
+
return !(0, milestone_chain_store_1.listActiveChains)(this.db).some((row) => row.current_rail_index === railIndex && (row.current_delivery_id !== null || (0, milestone_chain_store_1.parseRunIds)(row).length > 0));
|
|
188
|
+
}
|
|
189
|
+
/** Before allocating a new rail slot: a rail this chain already used, else
|
|
190
|
+
* any builder-named rail, that is free (its PR decided or nothing on it). */
|
|
191
|
+
freeBuilderRail(chain) {
|
|
192
|
+
try {
|
|
193
|
+
const own = chain ? (0, milestone_chain_store_1.parseLaunched)(chain).map((l) => l.railIndex) : [];
|
|
194
|
+
const builder = (this.io.listRails?.() ?? []).filter((r) => isBuilderRailName(r.name)).map((r) => r.railIndex);
|
|
195
|
+
for (const index of [...new Set([...own, ...builder])]) {
|
|
196
|
+
if (this.railFree(index))
|
|
197
|
+
return index;
|
|
198
|
+
}
|
|
199
|
+
return null;
|
|
135
200
|
}
|
|
136
201
|
catch {
|
|
137
202
|
return null;
|
|
@@ -151,10 +216,13 @@ class MilestoneChainManager {
|
|
|
151
216
|
return null;
|
|
152
217
|
}
|
|
153
218
|
}
|
|
154
|
-
|
|
155
|
-
|
|
219
|
+
/**
|
|
220
|
+
* Launch a milestone. Always sequential: one spec per rail, each stacked on
|
|
221
|
+
* the previous delivered branch. `autoAdvance` defaults to ON; turning it off
|
|
222
|
+
* parks the chain at a checkpoint after every delivered rail.
|
|
223
|
+
*/
|
|
224
|
+
async start(n, opts = {}) {
|
|
156
225
|
const autoAdvance = opts.autoAdvance !== false;
|
|
157
|
-
const mode = enabled ? requestedMode : 'parallel';
|
|
158
226
|
const active = (0, milestone_chain_store_1.listActiveChains)(this.db).find((c) => c.milestone_n === n);
|
|
159
227
|
if (active)
|
|
160
228
|
return { ok: false, status: 409, error: 'chain_active', chainId: active.id };
|
|
@@ -166,40 +234,15 @@ class MilestoneChainManager {
|
|
|
166
234
|
if (ticketIds.length === 0)
|
|
167
235
|
return { ok: false, status: 400, error: 'no_tickets', detail: `no todo specs labeled ${(0, milestone_progress_1.milestoneLabelFor)(n)}` };
|
|
168
236
|
const chunks = chunkTickets(ticketIds);
|
|
169
|
-
if (mode === 'parallel') {
|
|
170
|
-
const launched = [];
|
|
171
|
-
let failure = null;
|
|
172
|
-
for (let i = 0; i < chunks.length; i++) {
|
|
173
|
-
const r = await this.launchChunk(n, chunks, i, null);
|
|
174
|
-
if (!r.ok) {
|
|
175
|
-
failure = r;
|
|
176
|
-
break;
|
|
177
|
-
}
|
|
178
|
-
launched.push(r.entry);
|
|
179
|
-
}
|
|
180
|
-
if (launched.length === 0 && failure)
|
|
181
|
-
return { ok: false, status: failure.status, error: failure.error, detail: failure.detail };
|
|
182
|
-
let chainId = null;
|
|
183
|
-
if (enabled) {
|
|
184
|
-
// Recorded `completed` so parallel milestones show chunk ordering in
|
|
185
|
-
// the same progress model; never advances anything.
|
|
186
|
-
const row = (0, milestone_chain_store_1.createChain)(this.db, { id: (0, ids_1.newId)(), milestoneN: n, milestoneId: milestone.id, mode, chunks, integrationBranch: await this.safeIntegrationBranch(), status: 'completed', nowMs: this.now() });
|
|
187
|
-
(0, milestone_chain_store_1.updateChain)(this.db, row.id, 'completed', { nextChunk: launched.length, launched }, this.now());
|
|
188
|
-
chainId = row.id;
|
|
189
|
-
const fresh = (0, milestone_chain_store_1.getChain)(this.db, row.id);
|
|
190
|
-
if (fresh)
|
|
191
|
-
this.emit(fresh);
|
|
192
|
-
}
|
|
193
|
-
return { ok: true, status: 202, chainId, launched, pending: chunks.slice(launched.length) };
|
|
194
|
-
}
|
|
195
237
|
const row = (0, milestone_chain_store_1.createChain)(this.db, {
|
|
196
|
-
id: (0, ids_1.newId)(), milestoneN: n, milestoneId: milestone.id, mode, chunks,
|
|
238
|
+
id: (0, ids_1.newId)(), milestoneN: n, milestoneId: milestone.id, mode: 'sequential', chunks,
|
|
197
239
|
integrationBranch: await this.safeIntegrationBranch(), autoAdvance, nowMs: this.now(),
|
|
198
240
|
});
|
|
199
|
-
const first = await this.launchChunk(n, chunks, 0, null);
|
|
241
|
+
const first = await this.launchChunk(n, chunks, 0, null, null, undefined, row);
|
|
200
242
|
if (!first.ok) {
|
|
201
|
-
// Nothing launched ⇒ no chain remains active (the route relays the guard
|
|
202
|
-
|
|
243
|
+
// Nothing launched ⇒ no chain remains active (the route relays the guard,
|
|
244
|
+
// including `rail_limit_reached` when every rail holds undecided work).
|
|
245
|
+
(0, milestone_chain_store_1.updateChain)(this.db, row.id, 'running', { status: 'cancelled', pauseReason: first.error === exports.RAIL_LIMIT_REACHED ? exports.RAIL_LIMIT_REACHED : `launch_rejected:${first.error}` }, this.now());
|
|
203
246
|
return { ok: false, status: first.status, error: first.error, detail: first.detail, chainId: row.id };
|
|
204
247
|
}
|
|
205
248
|
this.recordLaunched(row.id, first.entry);
|
|
@@ -272,12 +315,17 @@ class MilestoneChainManager {
|
|
|
272
315
|
}
|
|
273
316
|
(0, milestone_chain_store_1.updateChain)(this.db, row.id, row.status, { status: 'waiting', headBranch: head, pauseReason: null }, this.now());
|
|
274
317
|
const reuse = chunkIndex < row.next_chunk ? this.reusableRailFor(row, chunkIndex) : null;
|
|
275
|
-
const r = await this.launchChunk(row.milestone_n, chunks, chunkIndex, head, reuse, multiRepositoryHistory ? earlierDeliveries : undefined);
|
|
318
|
+
const r = await this.launchChunk(row.milestone_n, chunks, chunkIndex, head, reuse, multiRepositoryHistory ? earlierDeliveries : undefined, row);
|
|
276
319
|
const current = (0, milestone_chain_store_1.getChain)(this.db, chainId);
|
|
277
320
|
if (!current || !(0, milestone_chain_store_1.isActiveChainStatus)(current.status))
|
|
278
321
|
return;
|
|
279
322
|
if (!r.ok) {
|
|
280
|
-
|
|
323
|
+
// Every rail holds undecided work: wait for the user to decide pending
|
|
324
|
+
// PRs, then Resume launches this same spec.
|
|
325
|
+
if (r.error === exports.RAIL_LIMIT_REACHED)
|
|
326
|
+
this.pause(current, exports.RAIL_LIMIT_REACHED);
|
|
327
|
+
else
|
|
328
|
+
this.pause(current, `launch_rejected:${r.error}`);
|
|
281
329
|
return;
|
|
282
330
|
}
|
|
283
331
|
this.recordLaunched(chainId, r.entry);
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.validateDefinitionForkRequest = validateDefinitionForkRequest;
|
|
4
|
+
exports.forkDefinitionRun = forkDefinitionRun;
|
|
5
|
+
const node_crypto_1 = require("node:crypto");
|
|
6
|
+
const node_fs_1 = require("node:fs");
|
|
7
|
+
const node_path_1 = require("node:path");
|
|
8
|
+
const db_1 = require("../../../db");
|
|
9
|
+
const agent_runtime_bridge_1 = require("../../agent-runtime/runtime/agent-runtime-bridge");
|
|
10
|
+
const loop_runs_store_1 = require("../../loops/runtime/loop-runs-store");
|
|
11
|
+
const loop_definition_recovery_1 = require("../../loops/runtime/loop-definition-recovery");
|
|
12
|
+
const rail_pr_store_1 = require("./rail-pr-store");
|
|
13
|
+
const isolated_settlement_store_1 = require("./isolated-settlement-store");
|
|
14
|
+
const rail_worktrees_store_1 = require("./rail-worktrees-store");
|
|
15
|
+
const object = (value) => !!value && typeof value === 'object' && !Array.isArray(value);
|
|
16
|
+
const canonical = (value) => Array.isArray(value) ? value.map(canonical) : object(value)
|
|
17
|
+
? Object.fromEntries(Object.keys(value).sort().map(key => [key, canonical(value[key])])) : value;
|
|
18
|
+
function validateDefinitionForkRequest(value) {
|
|
19
|
+
if (!object(value) || Object.keys(value).some(key => !['requestId', 'fromNodePath', 'scopeId', 'visit', 'state'].includes(key)) ||
|
|
20
|
+
typeof value.requestId !== 'string' || !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(value.requestId) ||
|
|
21
|
+
typeof value.fromNodePath !== 'string' || !value.fromNodePath.trim() || value.fromNodePath.length > 2048 ||
|
|
22
|
+
value.scopeId !== undefined && (typeof value.scopeId !== 'string' || !value.scopeId || value.scopeId.length > 2048) ||
|
|
23
|
+
value.visit !== undefined && (!Number.isSafeInteger(value.visit) || Number(value.visit) < 1) ||
|
|
24
|
+
value.state !== undefined && (!object(value.state) || Object.keys(value.state).some(key => !['$vars', '$outputs'].includes(key)) || Object.values(value.state).some(channel => !object(channel))))
|
|
25
|
+
throw new Error('invalid_fork_request');
|
|
26
|
+
if (JSON.stringify(value).length > 2 * 1024 * 1024)
|
|
27
|
+
throw new Error('invalid_fork_request: State exceeds 2 MiB');
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
/** Core owns the historical cut; Desktop atomically moves only host ownership.
|
|
31
|
+
* A durable request precedes Core publication so a lost acknowledgement can be
|
|
32
|
+
* retried. The source job, frozen inputs, events and accounting remain untouched.
|
|
33
|
+
*/
|
|
34
|
+
async function forkDefinitionRun(ctx, runId, input, io = {}) {
|
|
35
|
+
const db = ctx.db, projectId = ctx.project.id;
|
|
36
|
+
const frozen = (0, loop_runs_store_1.readDefinitionRun)(db, runId), sourceJob = (0, db_1.getJob)(db, runId);
|
|
37
|
+
if (!frozen || frozen.row.project_id !== projectId || !frozen.metadata.contextPath || !sourceJob)
|
|
38
|
+
throw new Error('runtime_run_not_found');
|
|
39
|
+
const request = validateDefinitionForkRequest(input), encoded = JSON.stringify(canonical(request));
|
|
40
|
+
const prior = db.prepare('SELECT * FROM definition_fork_operations WHERE project_id=? AND source_run_id=? AND request_id=?').get(projectId, runId, request.requestId);
|
|
41
|
+
if (prior && prior.request_json !== encoded)
|
|
42
|
+
throw new Error('fork_request_conflict');
|
|
43
|
+
if (prior?.adopted && prior.result_json)
|
|
44
|
+
return JSON.parse(prior.result_json);
|
|
45
|
+
if ((0, loop_runs_store_1.readDefinitionSuccessor)(db, runId))
|
|
46
|
+
throw new Error('runtime_fork_owns_worktree');
|
|
47
|
+
if (!prior && db.prepare('SELECT 1 FROM definition_fork_operations WHERE source_run_id=? LIMIT 1').get(runId))
|
|
48
|
+
throw new Error('fork_request_pending: Retry the original request');
|
|
49
|
+
const childRunId = prior?.child_run_id ?? `fork-${(0, node_crypto_1.createHash)('sha256').update(JSON.stringify([projectId, runId, request.requestId])).digest('hex').slice(0, 32)}`;
|
|
50
|
+
const observed = (0, loop_runs_store_1.readDefinitionExecutionClaim)(db, runId);
|
|
51
|
+
const probe = await (io.probe ?? loop_definition_recovery_1.probeDefinitionRun)({ db, cwd: ctx.project.path, env: process.env }, runId);
|
|
52
|
+
if (probe.status === 'unavailable')
|
|
53
|
+
throw new Error('runtime_status_unavailable');
|
|
54
|
+
if (probe.lease?.active || ctx.loopRunManager.isDefinitionCancellationPending(runId))
|
|
55
|
+
throw new Error('runtime_run_active');
|
|
56
|
+
if ((0, loop_runs_store_1.readDefinitionRun)(db, runId)?.row.status === 'running')
|
|
57
|
+
throw new Error('runtime_run_active: Wait for the host pause or terminal settlement');
|
|
58
|
+
if (observed && !ctx.loopRunManager.isDefinitionRunActive(runId))
|
|
59
|
+
db.prepare(`DELETE FROM definition_execution_claims WHERE run_id=? AND owner=?
|
|
60
|
+
AND EXISTS (SELECT 1 FROM loop_runs WHERE id=? AND status='paused' AND restart_reason='restart')`).run(runId, observed.owner, runId);
|
|
61
|
+
const token = `fork:${(0, node_crypto_1.randomUUID)()}`;
|
|
62
|
+
const execution = (0, loop_runs_store_1.claimDefinitionExecution)(db, runId, { owner: token, repositoryMounts: (0, loop_runs_store_1.definitionRepositoryMounts)(frozen.request) });
|
|
63
|
+
if (!execution.ok)
|
|
64
|
+
throw new Error('runtime_run_active');
|
|
65
|
+
const claimed = [];
|
|
66
|
+
try {
|
|
67
|
+
const owned = db.prepare('SELECT delivery_id FROM definition_delivery_settlements WHERE project_id=? AND run_id=? AND superseded_by IS NULL').all(projectId, runId);
|
|
68
|
+
const deliveries = owned.map(row => (0, rail_pr_store_1.getPrDelivery)(db, row.delivery_id));
|
|
69
|
+
const parentIds = deliveries.flatMap(row => row?.parent_delivery_id ? [row.parent_delivery_id] : []);
|
|
70
|
+
const all = [...deliveries, ...[...new Set(parentIds)].map(id => (0, rail_pr_store_1.getPrDelivery)(db, id))];
|
|
71
|
+
if (frozen.request.deferTerminalOutcome && !deliveries.length)
|
|
72
|
+
throw new Error('Original isolated settlement snapshot is unavailable');
|
|
73
|
+
if (frozen.row.status === 'completed' && deliveries.some(row => !(0, isolated_settlement_store_1.readIsolatedSettlementRecords)(db, projectId, row.id).find(record => record.snapshot.run.runId === runId)?.result))
|
|
74
|
+
throw new Error('runtime_settlement_pending: Finish original settlement before forking');
|
|
75
|
+
if (all.some(row => !row || ['discarded', 'superseded', 'merged', 'completed'].includes(row.decision)))
|
|
76
|
+
throw new Error('Delivery ownership has already changed');
|
|
77
|
+
const host = (0, agent_runtime_bridge_1.readFrozenRuntimeHost)(frozen.metadata.contextPath, process.env, runId);
|
|
78
|
+
const forkOptions = { ...request, kind: 'fork', runId, childRunId, contextPath: frozen.metadata.contextPath, cwd: host.cwd, env: host.env };
|
|
79
|
+
if (!io.fork)
|
|
80
|
+
await (0, agent_runtime_bridge_1.assertRetainedForkSupport)(forkOptions);
|
|
81
|
+
db.transaction(() => {
|
|
82
|
+
for (const row of all) {
|
|
83
|
+
if (!(0, rail_pr_store_1.claimPrDeliveryOperation)(db, row.id, row.decision, 'recover-and-retry', token))
|
|
84
|
+
throw new Error('Delivery is busy');
|
|
85
|
+
claimed.push(row);
|
|
86
|
+
}
|
|
87
|
+
if (!prior)
|
|
88
|
+
db.prepare('INSERT INTO definition_fork_operations(project_id,source_run_id,request_id,child_run_id,request_json) VALUES (?,?,?,?,?)').run(projectId, runId, request.requestId, childRunId, encoded);
|
|
89
|
+
})();
|
|
90
|
+
let fork;
|
|
91
|
+
try {
|
|
92
|
+
fork = io.fork ? await io.fork(forkOptions) : await (0, agent_runtime_bridge_1.runAgentRuntimeControl)(forkOptions);
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
// An authoritative pre-publication rejection is not a lost receipt. Only
|
|
96
|
+
// drop its intent when the destination does not exist; uncertain failures
|
|
97
|
+
// and all published/partial children keep the exact retry identity.
|
|
98
|
+
if (error instanceof Error && /^(fork_ambiguous|fork_checkpoint_missing|invalid_arguments|resume_incompatible|lease_held):/.test(error.message) &&
|
|
99
|
+
!(0, node_fs_1.existsSync)((0, node_path_1.join)((0, node_path_1.dirname)((0, node_path_1.dirname)(frozen.metadata.contextPath)), childRunId))) {
|
|
100
|
+
db.prepare('DELETE FROM definition_fork_operations WHERE project_id=? AND source_run_id=? AND request_id=? AND adopted=0 AND result_json IS NULL').run(projectId, runId, request.requestId);
|
|
101
|
+
}
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
if (fork.runId !== childRunId || fork.forkOf !== runId)
|
|
105
|
+
throw new Error('Core returned another fork identity');
|
|
106
|
+
const result = { loopRunId: childRunId, forkOf: runId, fromNodePath: fork.fromNodePath, scopeId: fork.scopeId, visit: fork.visit };
|
|
107
|
+
db.transaction(() => {
|
|
108
|
+
// Recheck all ownership after asynchronous Core work; a failed adoption
|
|
109
|
+
// rolls back entirely and retains the published child for the same retry.
|
|
110
|
+
const tickets = JSON.parse(frozen.row.ticket_ids_json ?? '[]');
|
|
111
|
+
for (const ticket of tickets) {
|
|
112
|
+
const owner = db.prepare('SELECT owner_id FROM ticket_outcome_ownership WHERE ticket_id=?').get(ticket);
|
|
113
|
+
if (owner?.owner_id !== runId)
|
|
114
|
+
throw new Error('Ticket ownership changed before fork adoption');
|
|
115
|
+
}
|
|
116
|
+
(0, loop_runs_store_1.createLoopRun)(db, { id: childRunId, projectId, loopId: frozen.row.loop_id, loopName: frozen.row.loop_name, railIndex: frozen.row.rail_index,
|
|
117
|
+
ticketId: frozen.row.ticket_id, ticketIds: tickets, ticketCompletionStatus: frozen.row.ticket_completion_status, causalOwnership: !!frozen.row.causal_ownership,
|
|
118
|
+
provider: frozen.row.provider, model: frozen.row.model, reasoningEffort: frozen.row.reasoning_effort, iterationLimit: frozen.row.iteration_limit, startedAt: new Date().toISOString() });
|
|
119
|
+
(0, db_1.createJob)(db, { id: childRunId, command: sourceJob.command, owner: 'loop', provider: sourceJob.provider, causal_ownership: !!frozen.row.causal_ownership, started_at: new Date().toISOString() });
|
|
120
|
+
(0, loop_runs_store_1.saveDefinitionRun)(db, childRunId, { ...frozen.metadata, contextPath: fork.contextPath, runtimeDirectory: fork.runtimeDirectory,
|
|
121
|
+
definitionPath: fork.definitionPath, configPath: fork.configPath, context: fork.context,
|
|
122
|
+
inheritedAddendaClaims: [...(frozen.metadata.inheritedAddendaClaims ?? []), ...(frozen.request.addenda?.ids.length ? [{ runId, ids: frozen.request.addenda.ids }] : [])],
|
|
123
|
+
request: { ...frozen.request, runId: childRunId } });
|
|
124
|
+
(0, loop_runs_store_1.linkDefinitionFork)(db, childRunId, { forkOf: runId, forkCut: { fromNodePath: fork.fromNodePath, scopeId: fork.scopeId, visit: fork.visit, revision: fork.revision } });
|
|
125
|
+
db.prepare("UPDATE loop_runs SET status='paused',restart_reason='fork' WHERE id=?").run(childRunId);
|
|
126
|
+
if (frozen.request.executionManifest)
|
|
127
|
+
db.prepare('UPDATE loop_runs SET execution_manifest=? WHERE id=?').run(JSON.stringify(frozen.request.executionManifest), childRunId);
|
|
128
|
+
db.prepare("UPDATE ticket_outcome_ownership SET owner_id=?,generation=generation+1,claimed_at=datetime('now') WHERE owner_id=?").run(childRunId, runId);
|
|
129
|
+
db.prepare("UPDATE rail_ticket_ownership SET owner_id=?,generation=generation+1,claimed_at=datetime('now') WHERE owner_id=?").run(childRunId, runId);
|
|
130
|
+
for (const row of deliveries) {
|
|
131
|
+
const source = (0, isolated_settlement_store_1.readIsolatedSettlementRecords)(db, projectId, row.id).find(record => record.snapshot.run.runId === runId);
|
|
132
|
+
if (!source)
|
|
133
|
+
throw new Error('Original isolated allocation is missing');
|
|
134
|
+
const snapshot = source.snapshot, ledger = (0, rail_worktrees_store_1.getRailWorktree)(db, snapshot.run.ledgerId);
|
|
135
|
+
if (!ledger || ledger.run_id !== runId || ledger.branch !== snapshot.run.handle.branch || ledger.worktree_path !== snapshot.run.handle.worktreePath)
|
|
136
|
+
throw new Error('Worktree ownership changed before fork adoption');
|
|
137
|
+
(0, isolated_settlement_store_1.saveIsolatedSettlementSnapshot)(db, { ...snapshot, run: { ...snapshot.run, runId: childRunId } });
|
|
138
|
+
db.prepare('UPDATE definition_delivery_settlements SET superseded_by=? WHERE project_id=? AND delivery_id=? AND run_id=? AND superseded_by IS NULL').run(childRunId, projectId, row.id, runId);
|
|
139
|
+
db.prepare("UPDATE rail_worktrees SET run_id=?,merge_state='building' WHERE id=? AND run_id=?").run(childRunId, ledger.id, runId);
|
|
140
|
+
}
|
|
141
|
+
for (const row of all) {
|
|
142
|
+
const runIds = JSON.parse(row.run_ids).map(id => id === runId ? childRunId : id);
|
|
143
|
+
if (!runIds.includes(childRunId))
|
|
144
|
+
runIds.push(childRunId);
|
|
145
|
+
const branches = JSON.parse(row.branches).filter(branch => branch.runId !== runId);
|
|
146
|
+
if (!(0, rail_pr_store_1.transitionClaimedDecision)(db, row.id, row.decision, 'building', token, { runIds, branches, implementationOutcome: 'unknown', deliveryOutcome: 'not_started', statusCode: 'restart_pending', statusDetail: 'Fork created; resume the linked run to continue.', settleEvidence: null }))
|
|
147
|
+
throw new Error('Delivery changed before fork adoption');
|
|
148
|
+
}
|
|
149
|
+
db.prepare('UPDATE definition_fork_operations SET result_json=?,adopted=1 WHERE project_id=? AND source_run_id=? AND request_id=?').run(JSON.stringify(result), projectId, runId, request.requestId);
|
|
150
|
+
})();
|
|
151
|
+
return result;
|
|
152
|
+
}
|
|
153
|
+
finally {
|
|
154
|
+
for (const row of claimed)
|
|
155
|
+
(0, rail_pr_store_1.releasePrDeliveryOperation)(db, row.id, token);
|
|
156
|
+
execution.release();
|
|
157
|
+
}
|
|
158
|
+
}
|
|
@@ -8,6 +8,7 @@ exports.extractVerifyStepText = extractVerifyStepText;
|
|
|
8
8
|
exports.parseConfidenceScore = parseConfidenceScore;
|
|
9
9
|
exports.harvestDeliveryEvidence = harvestDeliveryEvidence;
|
|
10
10
|
exports.readRuntimeEvidence = readRuntimeEvidence;
|
|
11
|
+
exports.projectDefinitionRuntimeEvidence = projectDefinitionRuntimeEvidence;
|
|
11
12
|
exports.healRuntimeEvidence = healRuntimeEvidence;
|
|
12
13
|
exports.readSettleEvidence = readSettleEvidence;
|
|
13
14
|
/**
|
|
@@ -283,12 +284,13 @@ function harvestDeliveryEvidence(io, units) {
|
|
|
283
284
|
const evidence = {
|
|
284
285
|
ticketId: unit.ticketId,
|
|
285
286
|
runId: unit.runId,
|
|
287
|
+
...(unit.reviewerStepId ? { reviewerStepId: unit.reviewerStepId } : {}),
|
|
286
288
|
sentinel: 'absent',
|
|
287
289
|
sentinelDetail: null,
|
|
288
290
|
verifyTail: null,
|
|
289
291
|
confidence: null,
|
|
290
292
|
};
|
|
291
|
-
let unitErrored =
|
|
293
|
+
let unitErrored = unit.definitionStatus?.status === 'unavailable';
|
|
292
294
|
let verifyStartedAtMs = null;
|
|
293
295
|
try {
|
|
294
296
|
if (unit.runId) {
|
|
@@ -321,8 +323,12 @@ function harvestDeliveryEvidence(io, units) {
|
|
|
321
323
|
unitErrored = true;
|
|
322
324
|
}
|
|
323
325
|
try {
|
|
324
|
-
if (unit.
|
|
325
|
-
const runtime =
|
|
326
|
+
if (unit.runId && (unit.definitionStatus || unit.runtimeDir)) {
|
|
327
|
+
const runtime = unit.definitionStatus
|
|
328
|
+
? projectDefinitionRuntimeEvidence(unit.definitionStatus, unit.reviewerStepId)
|
|
329
|
+
: readRuntimeEvidence(unit.runtimeDir, unit.runId, fsIo);
|
|
330
|
+
if (unit.definitionStatus)
|
|
331
|
+
evidence.runtime = runtime;
|
|
326
332
|
if (runtime) {
|
|
327
333
|
evidence.runtime = runtime;
|
|
328
334
|
// The runtime reviewer's structured verdict IS the reviewer score for
|
|
@@ -427,11 +433,18 @@ function readRuntimeEvidence(runtimeDir, runId, fsIo) {
|
|
|
427
433
|
checks.push({ name, status: str(raw.status, 40) ?? 'unknown', required: raw.required === true, hostRun, evidence, scope: str(raw.scope), limitations: str(raw.limitations) });
|
|
428
434
|
}
|
|
429
435
|
const findings = strList(acceptance?.findings, 6);
|
|
430
|
-
let review = null;
|
|
431
436
|
const checkpoint = readJson(fsIo, path_1.default.join(runtimeDir, 'agent-workflow', runId, 'checkpoint.json'));
|
|
432
437
|
const steps = isRecord(checkpoint) && isRecord(checkpoint.state) && isRecord(checkpoint.state.steps) ? checkpoint.state.steps : null;
|
|
433
438
|
const reviewer = steps && isRecord(steps.reviewer) ? steps.reviewer : null;
|
|
434
439
|
const output = reviewer && isRecord(reviewer.output) ? reviewer.output : null;
|
|
440
|
+
const review = parseRuntimeReview(output);
|
|
441
|
+
if (commands.length === 0 && checks.length === 0 && findings.length === 0 && !review)
|
|
442
|
+
return null;
|
|
443
|
+
return { commands, checks, findings, review };
|
|
444
|
+
}
|
|
445
|
+
/** A reviewer verdict stays AI-reported; it cannot create host verification. */
|
|
446
|
+
function parseRuntimeReview(value) {
|
|
447
|
+
const output = isRecord(value) ? value : null;
|
|
435
448
|
if (output && ('score' in output || 'approved' in output)) {
|
|
436
449
|
const aspects = {};
|
|
437
450
|
if (isRecord(output.aspects)) {
|
|
@@ -442,7 +455,7 @@ function readRuntimeEvidence(runtimeDir, runId, fsIo) {
|
|
|
442
455
|
const issues = Array.isArray(output.issues)
|
|
443
456
|
? output.issues.slice(0, 6).map((i) => (typeof i === 'string' ? i : isRecord(i) ? (str(i.message) ?? str(i.title) ?? str(i.description) ?? JSON.stringify(i).slice(0, RUNTIME_TEXT_CAP)) : null)).filter((x) => !!x)
|
|
444
457
|
: [];
|
|
445
|
-
|
|
458
|
+
return {
|
|
446
459
|
approved: typeof output.approved === 'boolean' ? output.approved : null,
|
|
447
460
|
score: typeof output.score === 'number' && Number.isFinite(output.score) ? output.score : null,
|
|
448
461
|
aspects,
|
|
@@ -450,9 +463,39 @@ function readRuntimeEvidence(runtimeDir, runId, fsIo) {
|
|
|
450
463
|
summary: str(output.summary, 1200),
|
|
451
464
|
};
|
|
452
465
|
}
|
|
453
|
-
|
|
466
|
+
return null;
|
|
467
|
+
}
|
|
468
|
+
/** Pure projection of exact committed scopes; never read Core's private database. */
|
|
469
|
+
function projectDefinitionRuntimeEvidence(status, reviewerStepId) {
|
|
470
|
+
if (status.status === 'unavailable' || !status.scopes)
|
|
454
471
|
return null;
|
|
455
|
-
|
|
472
|
+
const commands = [];
|
|
473
|
+
const scopedReviews = [];
|
|
474
|
+
for (const scope of status.scopes) {
|
|
475
|
+
if (!['succeeded', 'failed'].includes(scope.status) || !scope.attemptId || !isRecord(scope.output))
|
|
476
|
+
continue;
|
|
477
|
+
const output = scope.output;
|
|
478
|
+
if (scope.kind === 'verify' && typeof output.receiptId === 'string' && Array.isArray(output.commands)) {
|
|
479
|
+
for (const raw of output.commands) {
|
|
480
|
+
if (commands.length >= RUNTIME_LIST_CAP)
|
|
481
|
+
break;
|
|
482
|
+
if (!isRecord(raw) || typeof raw.command !== 'string' || !Number.isInteger(raw.exitCode))
|
|
483
|
+
continue;
|
|
484
|
+
const exitCode = raw.exitCode;
|
|
485
|
+
commands.push({ label: raw.command.slice(0, 200), outcome: exitCode === 0 ? 'passed' : 'failed', exitCode,
|
|
486
|
+
durationMs: null, required: true, hostRun: true, outputTail: null });
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
const review = scope.status !== 'succeeded' ? null : scope.kind === 'implementation' ? parseRuntimeReview(output.review)
|
|
490
|
+
: reviewerStepId === scope.nodePath && scope.kind === 'role-turn' ? parseRuntimeReview(output.structured ?? output) : null;
|
|
491
|
+
if (review)
|
|
492
|
+
scopedReviews.push({ nodePath: scope.nodePath, scopeId: scope.scopeId, attemptId: scope.attemptId, review });
|
|
493
|
+
}
|
|
494
|
+
// Multiple branch verdicts have no defined aggregate confidence score.
|
|
495
|
+
// Keep that absence explicit instead of letting the last branch win.
|
|
496
|
+
const review = scopedReviews.length === 1 ? scopedReviews[0].review : null;
|
|
497
|
+
const findings = scopedReviews.length > 1 ? ['Multiple scoped reviewer verdicts; no aggregate confidence score is defined.'] : [];
|
|
498
|
+
return commands.length || review || findings.length ? { commands, checks: [], findings, review, scopedReviews } : null;
|
|
456
499
|
}
|
|
457
500
|
/**
|
|
458
501
|
* Heal a persisted settle evidence whose units predate the runtime harvest:
|
|
@@ -460,7 +503,7 @@ function readRuntimeEvidence(runtimeDir, runId, fsIo) {
|
|
|
460
503
|
* now. Returns the healed evidence when anything was added, else null (so the
|
|
461
504
|
* caller neither rewrites the row nor re-broadcasts). Pure apart from `fsIo`.
|
|
462
505
|
*/
|
|
463
|
-
function healRuntimeEvidence(evidence, pipelineDir, fsIo = {}) {
|
|
506
|
+
function healRuntimeEvidence(evidence, pipelineDir, fsIo = {}, definitionStatuses = new Map()) {
|
|
464
507
|
const io = {
|
|
465
508
|
readFile: fsIo.readFile ?? ((p) => fs_1.default.readFileSync(p, 'utf8')),
|
|
466
509
|
listDir: fsIo.listDir ?? ((p) => fs_1.default.readdirSync(p)),
|
|
@@ -469,11 +512,14 @@ function healRuntimeEvidence(evidence, pipelineDir, fsIo = {}) {
|
|
|
469
512
|
};
|
|
470
513
|
let changed = false;
|
|
471
514
|
const units = evidence.units.map((unit) => {
|
|
472
|
-
if (unit.
|
|
515
|
+
if (!unit.runId || unit.runtime)
|
|
516
|
+
return unit;
|
|
517
|
+
const definition = definitionStatuses.get(unit.runId);
|
|
518
|
+
if (definition?.status === 'unavailable' || !definition && unit.runtime !== undefined)
|
|
473
519
|
return unit;
|
|
474
520
|
let runtime = null;
|
|
475
521
|
try {
|
|
476
|
-
runtime = readRuntimeEvidence(path_1.default.join(pipelineDir, unit.runId), unit.runId, io);
|
|
522
|
+
runtime = definition ? projectDefinitionRuntimeEvidence(definition, unit.reviewerStepId) : readRuntimeEvidence(path_1.default.join(pipelineDir, unit.runId), unit.runId, io);
|
|
477
523
|
}
|
|
478
524
|
catch {
|
|
479
525
|
runtime = null;
|