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
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.compileLoopToDefinition = compileLoopToDefinition;
|
|
4
|
+
const loop_command_catalog_1 = require("./loop-command-catalog");
|
|
5
|
+
const loop_constants_1 = require("./loop-constants");
|
|
6
|
+
const loop_graph_1 = require("./loop-graph");
|
|
7
|
+
const AI_KINDS = new Set(['prompt', 'role-turn', 'decider', 'implementation']);
|
|
8
|
+
const object = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
9
|
+
/** No I/O, model call, hash implementation or scheduling belongs in this adapter. */
|
|
10
|
+
function compileLoopToDefinition(graph, launch) {
|
|
11
|
+
(0, loop_graph_1.assertDefinitionGraph)(graph);
|
|
12
|
+
const allGraphs = [graph, ...Object.values(graph.components ?? {})];
|
|
13
|
+
for (const body of allGraphs) {
|
|
14
|
+
(0, loop_graph_1.assertDefinitionGraph)(body);
|
|
15
|
+
const validation = (0, loop_graph_1.validateLoopGraph)(body);
|
|
16
|
+
if (!validation.valid)
|
|
17
|
+
throw new Error(validation.errors.map((error) => `${error.nodeId ?? 'graph'}: ${error.message}`).join('\n'));
|
|
18
|
+
if (body !== graph && body.components && Object.keys(body.components).length) {
|
|
19
|
+
throw new Error('Reusable components belong in the top-level component library; nested pieces reference that library.');
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const writes = allGraphs.some((body) => body.nodes.some((node) => pieceWrites(node, launch.roles)));
|
|
23
|
+
const roleIds = new Set();
|
|
24
|
+
const compileBody = (body) => {
|
|
25
|
+
const start = body.nodes.find((node) => node.type === 'start');
|
|
26
|
+
const entry = body.edges.find((edge) => edge.source === start.id).target;
|
|
27
|
+
if (body.edges.some((edge) => edge.target === start.id))
|
|
28
|
+
throw new Error('A Core workflow cannot route back to the visual Start node.');
|
|
29
|
+
const nodes = {};
|
|
30
|
+
for (const node of [...body.nodes].sort((a, b) => a.id.localeCompare(b.id, 'en'))) {
|
|
31
|
+
if (node.type === 'start')
|
|
32
|
+
continue;
|
|
33
|
+
const kind = node.type === 'end' ? 'end' : node.data.kind;
|
|
34
|
+
const rawParams = node.type === 'end'
|
|
35
|
+
? {
|
|
36
|
+
outcome: node.data?.outcome ?? 'success',
|
|
37
|
+
...(typeof node.data?.requiresVerified === 'boolean'
|
|
38
|
+
? { requiresVerified: node.data.requiresVerified }
|
|
39
|
+
: {}),
|
|
40
|
+
...(typeof node.data?.exit === 'string' ? { exit: node.data.exit } : {}),
|
|
41
|
+
...(typeof node.data?.reason === 'string' ? { reason: node.data.reason } : {}),
|
|
42
|
+
}
|
|
43
|
+
: node.data.params;
|
|
44
|
+
const engineInput = object(rawParams.engine) ? rawParams.engine : {};
|
|
45
|
+
const provider = typeof engineInput.provider === 'string' ? engineInput.provider : launch.provider;
|
|
46
|
+
const params = resolveValue(rawParams, launch, provider);
|
|
47
|
+
if (kind === 'prompt') {
|
|
48
|
+
params.engine = {
|
|
49
|
+
provider,
|
|
50
|
+
...(provider === launch.provider && launch.model ? { model: launch.model } : {}),
|
|
51
|
+
...(provider === launch.provider && launch.effort ? { effort: launch.effort } : {}),
|
|
52
|
+
...engineInput,
|
|
53
|
+
};
|
|
54
|
+
if (typeof rawParams.text === 'string' &&
|
|
55
|
+
/^\s*\{\{\s*cmd:[\w:-]+\s*\}\}/.test(rawParams.text) &&
|
|
56
|
+
typeof params.text === 'string') {
|
|
57
|
+
const native = params.text.match(/^\s*(?:\/|\$)([a-z][a-z0-9:_-]*)(?:\s+([\s\S]*))?$/);
|
|
58
|
+
if (native) {
|
|
59
|
+
params.nativeCommand = { id: native[1].replace(/^skill:/, ''), args: native[2] ?? '' };
|
|
60
|
+
delete params.text;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (launch.briefing && ['prompt', 'role-turn', 'decider'].includes(kind)) {
|
|
65
|
+
const suffix = '\n\nFrozen launch context:\n' + launch.briefing;
|
|
66
|
+
if (object(params.nativeCommand))
|
|
67
|
+
params.nativeCommand = { ...params.nativeCommand, args: String(params.nativeCommand.args ?? '') + suffix };
|
|
68
|
+
else
|
|
69
|
+
for (const field of ['text', 'prompt', 'goal'])
|
|
70
|
+
if (typeof params[field] === 'string')
|
|
71
|
+
params[field] += suffix;
|
|
72
|
+
}
|
|
73
|
+
if (kind === 'implementation')
|
|
74
|
+
for (const role of ['architect', 'developer', 'reviewer'])
|
|
75
|
+
roleIds.add(role);
|
|
76
|
+
if ((kind === 'role-turn' || kind === 'decider') && typeof params.roleId === 'string')
|
|
77
|
+
roleIds.add(params.roleId);
|
|
78
|
+
if (kind === 'end' && params.outcome === 'success' && params.requiresVerified === undefined)
|
|
79
|
+
params.requiresVerified = writes;
|
|
80
|
+
const ends = Object.fromEntries(body.edges.filter((edge) => edge.source === node.id).map((edge) => [edge.label, edge.target]));
|
|
81
|
+
nodes[node.id] = {
|
|
82
|
+
kind,
|
|
83
|
+
params,
|
|
84
|
+
ends,
|
|
85
|
+
...(node.data?.retry
|
|
86
|
+
? {
|
|
87
|
+
retry: {
|
|
88
|
+
...node.data.retry,
|
|
89
|
+
...(node.data.retry.retryOn ? { retryOn: [...node.data.retry.retryOn] } : {}),
|
|
90
|
+
},
|
|
91
|
+
}
|
|
92
|
+
: {}),
|
|
93
|
+
...(typeof node.data?.label === 'string' ? { label: node.data.label } : {}),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
entry,
|
|
98
|
+
nodes,
|
|
99
|
+
maxTransitions: expandedTransitionBound(body, graph.components ?? {}, launch),
|
|
100
|
+
...(body.inputs ? { inputs: [...body.inputs] } : {}),
|
|
101
|
+
...(body.outputs ? { outputs: [...body.outputs] } : {}),
|
|
102
|
+
};
|
|
103
|
+
};
|
|
104
|
+
const body = compileBody(graph);
|
|
105
|
+
const components = Object.fromEntries(Object.entries(graph.components ?? {})
|
|
106
|
+
.sort(([a], [b]) => a.localeCompare(b, 'en'))
|
|
107
|
+
.map(([id, component]) => [id, compileBody(component)]));
|
|
108
|
+
const implementation = [body, ...Object.values(components)].some((component) => Object.values(component.nodes).some((node) => node.kind === 'implementation'));
|
|
109
|
+
const budget = {
|
|
110
|
+
...(graph.config.maxCostUsd && graph.config.maxCostUsd > 0
|
|
111
|
+
? { maxCostUsd: graph.config.maxCostUsd }
|
|
112
|
+
: {}),
|
|
113
|
+
...(graph.config.maxTokens ? { maxTokens: graph.config.maxTokens } : {}),
|
|
114
|
+
...(graph.config.timeoutMinutes > 0
|
|
115
|
+
? { maxDurationMs: Math.round(graph.config.timeoutMinutes * 60_000) }
|
|
116
|
+
: {}),
|
|
117
|
+
};
|
|
118
|
+
const id = (launch.id ?? 'desktop-workflow')
|
|
119
|
+
.toLowerCase()
|
|
120
|
+
.replace(/[^a-z0-9-]/g, '-')
|
|
121
|
+
.slice(0, 64);
|
|
122
|
+
if (!/^[a-z0-9]/.test(id))
|
|
123
|
+
throw new Error('Workflow id must start with a letter or number.');
|
|
124
|
+
return {
|
|
125
|
+
schemaVersion: 1,
|
|
126
|
+
id,
|
|
127
|
+
title: launch.title ?? 'Workflow',
|
|
128
|
+
journal: graph.config.journal ?? (implementation ? 'implementation' : 'ledger-only'),
|
|
129
|
+
change: graph.config.change ?? (implementation ? (launch.changeId ? 'existing' : 'new') : 'none'),
|
|
130
|
+
...body,
|
|
131
|
+
maxTransitions: body.maxTransitions,
|
|
132
|
+
roles: [...roleIds].sort(),
|
|
133
|
+
...(Object.keys(budget).length ? { budget } : {}),
|
|
134
|
+
...(graph.config.policies ? { policies: { ...graph.config.policies } } : {}),
|
|
135
|
+
...(Object.keys(components).length ? { components } : {}),
|
|
136
|
+
delivery: { requiresVerified: writes },
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function pieceWrites(node, roles) {
|
|
140
|
+
if (node.type !== 'core')
|
|
141
|
+
return false;
|
|
142
|
+
const kind = node.data?.kind, params = node.data?.params ?? {};
|
|
143
|
+
if (kind === 'prompt')
|
|
144
|
+
return params.access !== 'read';
|
|
145
|
+
if (kind === 'role-turn')
|
|
146
|
+
return roles?.[String(params.roleId)]?.access !== 'read';
|
|
147
|
+
return ['verify', 'shell', 'openspec-archive', 'implementation'].includes(kind ?? '');
|
|
148
|
+
}
|
|
149
|
+
/** Expand templates once. Text inserted by spec/constants cannot introduce another token. */
|
|
150
|
+
function resolveValue(value, launch, provider) {
|
|
151
|
+
if (typeof value === 'string') {
|
|
152
|
+
return value.replace(/\{\{\{\{|\{\{\s*(?:spec\.\w+|const:[A-Za-z0-9_.-]+|cmd:[\w:-]+)\s*\}\}/g, (token) => {
|
|
153
|
+
if (token === '{{{{')
|
|
154
|
+
return token; // Core resolves the literal escape at execution.
|
|
155
|
+
if (/^\{\{\s*spec\./.test(token))
|
|
156
|
+
return (0, loop_graph_1.interpolateSpec)(token, launch.spec);
|
|
157
|
+
if (/^\{\{\s*const:/.test(token))
|
|
158
|
+
return (0, loop_constants_1.resolveConstants)(token.replace(/\s+/g, ''), launch.constants);
|
|
159
|
+
return (0, loop_command_catalog_1.expandCommands)(token, { provider, ticketIds: launch.spec?.ticketIds, specId: launch.spec?.id });
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
if (Array.isArray(value))
|
|
163
|
+
return value.map((item) => resolveValue(item, launch, provider));
|
|
164
|
+
if (object(value))
|
|
165
|
+
return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, resolveValue(child, launch, provider)]));
|
|
166
|
+
return value;
|
|
167
|
+
}
|
|
168
|
+
/** Nested visits share Core's global budget. Dynamic fan-out requires an explicit cap or the engine ceiling. */
|
|
169
|
+
function expandedTransitionBound(body, library, launch, ancestors = []) {
|
|
170
|
+
if (body.config.maxTransitions !== undefined)
|
|
171
|
+
return body.config.maxTransitions;
|
|
172
|
+
const weights = new Map();
|
|
173
|
+
for (const node of body.nodes) {
|
|
174
|
+
const kind = node.data?.kind, params = node.data?.params ?? {};
|
|
175
|
+
if (kind === 'implementation' || (kind === 'map' && object(params.over)))
|
|
176
|
+
return 10_000;
|
|
177
|
+
if (kind !== 'component' && kind !== 'map')
|
|
178
|
+
continue;
|
|
179
|
+
const ref = String(kind === 'component' ? params.ref : params.body);
|
|
180
|
+
if (ancestors.includes(ref))
|
|
181
|
+
throw new Error('Reusable components cannot recursively reference themselves.');
|
|
182
|
+
const child = library[ref];
|
|
183
|
+
if (!child)
|
|
184
|
+
continue; // Core reports the exact missing-reference diagnostic.
|
|
185
|
+
const visits = expandedTransitionBound(child, library, launch, [...ancestors, ref]);
|
|
186
|
+
if (visits === 10_000)
|
|
187
|
+
return 10_000;
|
|
188
|
+
const count = kind === 'map'
|
|
189
|
+
? params.over === 'tickets'
|
|
190
|
+
? Math.max(1, launch.spec?.ticketIds?.length ?? 1)
|
|
191
|
+
: launch.repositoryCount
|
|
192
|
+
: 1;
|
|
193
|
+
if (count === undefined)
|
|
194
|
+
return 10_000;
|
|
195
|
+
weights.set(node.id, 1 + visits * count);
|
|
196
|
+
}
|
|
197
|
+
return transitionBound(body, weights);
|
|
198
|
+
}
|
|
199
|
+
/** Count the largest strongly connected region; retries have a separate bounded allowance. */
|
|
200
|
+
function transitionBound(graph, weights = new Map()) {
|
|
201
|
+
const nodes = graph.nodes.filter((node) => node.type !== 'start');
|
|
202
|
+
const adjacency = new Map(nodes.map((node) => [
|
|
203
|
+
node.id,
|
|
204
|
+
graph.edges.filter((edge) => edge.source === node.id).map((edge) => edge.target),
|
|
205
|
+
]));
|
|
206
|
+
let nextIndex = 0, largestCycle = 0, cyclicNodes = 0;
|
|
207
|
+
const indices = new Map(), low = new Map(), stack = [], active = new Set();
|
|
208
|
+
const visit = (id) => {
|
|
209
|
+
indices.set(id, nextIndex);
|
|
210
|
+
low.set(id, nextIndex++);
|
|
211
|
+
stack.push(id);
|
|
212
|
+
active.add(id);
|
|
213
|
+
for (const target of adjacency.get(id) ?? []) {
|
|
214
|
+
if (!indices.has(target)) {
|
|
215
|
+
visit(target);
|
|
216
|
+
low.set(id, Math.min(low.get(id), low.get(target)));
|
|
217
|
+
}
|
|
218
|
+
else if (active.has(target))
|
|
219
|
+
low.set(id, Math.min(low.get(id), indices.get(target)));
|
|
220
|
+
}
|
|
221
|
+
if (low.get(id) !== indices.get(id))
|
|
222
|
+
return;
|
|
223
|
+
let count = 0, members = 0, current;
|
|
224
|
+
do {
|
|
225
|
+
current = stack.pop();
|
|
226
|
+
active.delete(current);
|
|
227
|
+
count += weights.get(current) ?? 1;
|
|
228
|
+
members += 1;
|
|
229
|
+
} while (current !== id);
|
|
230
|
+
if (members > 1 || adjacency.get(id)?.includes(id)) {
|
|
231
|
+
largestCycle = Math.max(largestCycle, count);
|
|
232
|
+
cyclicNodes += count;
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
for (const node of nodes)
|
|
236
|
+
if (!indices.has(node.id))
|
|
237
|
+
visit(node.id);
|
|
238
|
+
const retryNodes = nodes.filter((node) => node.data?.retry ? (node.data.retry.maxAttempts ?? 1) > 1 : AI_KINDS.has(node.data?.kind)).length;
|
|
239
|
+
const bound = Math.max([...nodes].reduce((sum, node) => sum + (weights.get(node.id) ?? 1), 0), graph.config.maxIterations * largestCycle +
|
|
240
|
+
nodes.reduce((sum, node) => sum + (weights.get(node.id) ?? 1), 0) -
|
|
241
|
+
cyclicNodes) +
|
|
242
|
+
2 * retryNodes;
|
|
243
|
+
if (bound > 10_000)
|
|
244
|
+
throw new Error('Workflow transition budget exceeds Core limit of 10000.');
|
|
245
|
+
return Math.max(1, bound);
|
|
246
|
+
}
|
|
@@ -3,7 +3,16 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.classifyLoopEffect = classifyLoopEffect;
|
|
4
4
|
exports.loopIsMutating = loopIsMutating;
|
|
5
5
|
function classifyLoopEffect(graph) {
|
|
6
|
-
const writes = graph.nodes.some(
|
|
6
|
+
const writes = graph.nodes.some(n => {
|
|
7
|
+
if (n.type === 'ai-step' || n.type === 'shell')
|
|
8
|
+
return true;
|
|
9
|
+
if (n.type !== 'core')
|
|
10
|
+
return false;
|
|
11
|
+
if (n.data?.kind === 'prompt')
|
|
12
|
+
return n.data.params?.access !== 'read';
|
|
13
|
+
// Role access binds at launch; isolation is conservative before that binding.
|
|
14
|
+
return ['role-turn', 'verify', 'shell', 'openspec-archive', 'implementation', 'component', 'map'].includes(String(n.data?.kind));
|
|
15
|
+
}) || Object.values(graph.components ?? {}).some(component => classifyLoopEffect(component) === 'mutating');
|
|
7
16
|
return writes ? 'mutating' : 'read-only';
|
|
8
17
|
}
|
|
9
18
|
/** Convenience: true when the loop mutates the repo (derived from its nodes). */
|
|
@@ -9,6 +9,9 @@ exports.createLoopExecutors = createLoopExecutors;
|
|
|
9
9
|
* from coverage; the engine's traversal/decision logic is unit-tested against
|
|
10
10
|
* fake executors in `loop-run-manager.test.ts`.
|
|
11
11
|
*/
|
|
12
|
+
const loop_definition_1 = require("./loop-definition");
|
|
13
|
+
const agent_runtime_loader_1 = require("../../agent-runtime/runtime/agent-runtime-loader");
|
|
14
|
+
const agent_runtime_engines_1 = require("../../agent-runtime/runtime/agent-runtime-engines");
|
|
12
15
|
const core_completion_1 = require("../../../core-completion");
|
|
13
16
|
const core_execution_1 = require("../../../core-execution");
|
|
14
17
|
const agent_runtime_bridge_1 = require("../../agent-runtime/runtime/agent-runtime-bridge");
|
|
@@ -33,6 +36,7 @@ const repo_map_1 = require("../../../repo-map");
|
|
|
33
36
|
const feature_flags_1 = require("../../../feature-flags");
|
|
34
37
|
const loop_step_idle_1 = require("./loop-step-idle");
|
|
35
38
|
const runtime_1 = require("../../../providers/runtime");
|
|
39
|
+
const loop_run_manager_1 = require("./loop-run-manager");
|
|
36
40
|
const loop_shell_invocation_1 = require("./loop-shell-invocation");
|
|
37
41
|
// Per-step wall-clock caps so a single hung step can't block the engine's
|
|
38
42
|
// overall deadline check (which only runs between nodes).
|
|
@@ -177,6 +181,62 @@ function createLoopExecutors(opts = {}) {
|
|
|
177
181
|
const completionContexts = new Map();
|
|
178
182
|
const runtimeConfigPath = (cwd) => (0, node_path_1.join)(opts.pluginScope?.().stateRoot ?? cwd, '.specrails', 'agent-runtime.json');
|
|
179
183
|
return {
|
|
184
|
+
async cancelDefinition({ runId, contextPath, requestId }) {
|
|
185
|
+
const host = (0, agent_runtime_bridge_1.readFrozenRuntimeHost)(contextPath, resolveEnv(), runId);
|
|
186
|
+
await (0, agent_runtime_bridge_1.runAgentRuntimeControl)({ kind: 'cancel', runId, contextPath, requestId, ...host });
|
|
187
|
+
},
|
|
188
|
+
async assertDefinitionSupport() {
|
|
189
|
+
const runtime = await (0, agent_runtime_loader_1.loadCoreAgentRuntime)();
|
|
190
|
+
if (runtime.api?.capabilities?.engineV2 !== 1 || runtime.api?.capabilities?.workflowDefinitions !== 1)
|
|
191
|
+
throw new Error('engine_unsupported: Update Core to run workflow definitions');
|
|
192
|
+
},
|
|
193
|
+
async assertLegacyEngineSupport() {
|
|
194
|
+
// Legacy traversal predates Core: only a Core that positively lacks engine 1 blocks it.
|
|
195
|
+
let runtime;
|
|
196
|
+
try {
|
|
197
|
+
runtime = await (0, agent_runtime_loader_1.loadCoreAgentRuntime)();
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
if (!(0, agent_runtime_engines_1.supportedEngines)(runtime.api).includes(1))
|
|
203
|
+
throw new Error('legacy_engine_unavailable: The installed Core runs only workflow definitions. Convert this loop to Core first.');
|
|
204
|
+
},
|
|
205
|
+
async runDefinition(input) {
|
|
206
|
+
const { request, runId } = input;
|
|
207
|
+
if (input.resume) {
|
|
208
|
+
const contextPath = input.contextPath ?? runtimeContextPath(request.cwd, runId, resolveEnv());
|
|
209
|
+
const host = (0, agent_runtime_bridge_1.readFrozenRuntimeHost)(contextPath, resolveEnv(), runId);
|
|
210
|
+
const result = await (0, agent_runtime_bridge_1.runAgentRuntimeInvocation)({ contextPath, ...host, engineVersion: 2, resume: true, answer: input.answer, approve: input.approve, recover: input.recover, interruptId: input.interruptId, onLine: input.onLine, onRuntimeEvent: input.onRuntimeEvent, onSpawn: input.onSpawn, timeoutMs: input.timeoutMs });
|
|
211
|
+
return { ...result, runtimeStatus: result.runtimeStatus ?? 'failed' };
|
|
212
|
+
}
|
|
213
|
+
const seeded = (0, loop_run_manager_1.seedChangeId)({ ...request, runId });
|
|
214
|
+
const briefing = [request.followUp?.briefing, request.addenda?.briefing].filter(Boolean).join('\n\n');
|
|
215
|
+
const spec = request.spec && briefing ? { ...request.spec, description: [request.spec.description, briefing].filter(Boolean).join('\n\n'), ...(request.spec.tickets ? { tickets: request.spec.tickets.map(ticket => ({ ...ticket, description: [ticket.description, briefing].filter(Boolean).join('\n\n') })) } : {}) } : request.spec;
|
|
216
|
+
const env = { ...programmaticStepEnv(resolveEnv(), request.repoDir, request.executionManifest), SPECRAILS_GIT_AUTO: 'false' };
|
|
217
|
+
const core = (0, core_execution_1.prepareCoreExecution)({ run: { runId, projectId: request.projectId, repositoryId: request.repositoryId, spec, goal: request.spec?.title ?? request.loopName }, cwd: request.cwd, repoDir: request.repoDir, manifest: request.executionManifest, env, sourcePath: request.executionManifest ? undefined : opts.sourcePath?.() });
|
|
218
|
+
const result = await (0, agent_runtime_bridge_1.runAgentRuntimeInvocation)({
|
|
219
|
+
contextPath: core.contextPath, cwd: request.cwd, env: core.env,
|
|
220
|
+
configPath: runtimeConfigPath(request.cwd), engineVersion: 2,
|
|
221
|
+
change: seeded?.id ?? (0, agent_runtime_bridge_1.runtimeChangeName)(runId),
|
|
222
|
+
resume: input.resume, answer: input.answer, approve: input.approve, interruptId: input.interruptId,
|
|
223
|
+
defaultProvider: request.provider, providerOverride: request.runtimeProviderOverride,
|
|
224
|
+
...(request.graph.config.legacyDeciderRole ? { workflowRoleBindings: {
|
|
225
|
+
[request.graph.config.legacyDeciderRole]: {
|
|
226
|
+
...(request.deciderEngine ?? { provider: request.provider, model: request.model, effort: request.effort }),
|
|
227
|
+
access: 'read', artifacts: 'none',
|
|
228
|
+
},
|
|
229
|
+
} } : {}),
|
|
230
|
+
...(!input.resume ? { prepareDefinition: (config) => (0, loop_definition_1.compileLoopToDefinition)(request.graph, {
|
|
231
|
+
id: request.loopId, title: request.loopName, spec: request.spec,
|
|
232
|
+
constants: request.constants ?? {}, provider: request.provider, model: request.model, effort: request.effort,
|
|
233
|
+
roles: { architect: { access: 'read' }, developer: { access: 'write' }, reviewer: { access: 'read' }, ...config.roles }, repositoryCount: request.executionManifest?.repositories.length ?? 1,
|
|
234
|
+
changeId: seeded?.id, briefing,
|
|
235
|
+
}) } : {}),
|
|
236
|
+
onPrepared: input.onPrepared, onLine: input.onLine, onRuntimeEvent: input.onRuntimeEvent, onSpawn: input.onSpawn, timeoutMs: input.timeoutMs,
|
|
237
|
+
});
|
|
238
|
+
return { ...result, runtimeStatus: result.runtimeStatus ?? 'failed' };
|
|
239
|
+
},
|
|
180
240
|
async readCoreCompletion(runId) {
|
|
181
241
|
const context = completionContexts.get(runId);
|
|
182
242
|
completionContexts.delete(runId);
|
|
@@ -237,7 +297,7 @@ function createLoopExecutors(opts = {}) {
|
|
|
237
297
|
const existingContext = coreRun ? runtimeContextPath(cwd, coreRun.runId, baseEnv) : undefined;
|
|
238
298
|
const programmatic = coreRun && existingContext && (coreRun.implementation || (coreRun.verificationStep && existsRuntimeRequest(existingContext)));
|
|
239
299
|
const baseStepEnv = programmatic ? programmaticStepEnv(baseEnv, repoDir, executionManifest) : withProfileEnv(aiStepEnv(baseEnv, repoDir, executionManifest), provider, profileName);
|
|
240
|
-
const core = coreRun ? (0, core_execution_1.prepareCoreExecution)({ run: coreRun, cwd, repoDir, manifest: executionManifest, env: baseStepEnv }) : undefined;
|
|
300
|
+
const core = coreRun ? (0, core_execution_1.prepareCoreExecution)({ run: coreRun, cwd, repoDir, manifest: executionManifest, env: baseStepEnv, sourcePath: executionManifest ? undefined : opts.sourcePath?.() }) : undefined;
|
|
241
301
|
const stepEnv = core?.env ?? baseStepEnv;
|
|
242
302
|
if (coreRun?.implementation && core) {
|
|
243
303
|
completionContexts.set(coreRun.runId, { cwd, contextPath: core.contextPath, env: stepEnv, runId: coreRun.runId });
|
|
@@ -476,7 +536,7 @@ function createLoopExecutors(opts = {}) {
|
|
|
476
536
|
if (!adapter.capabilities.persistentStdin)
|
|
477
537
|
return null;
|
|
478
538
|
const baseStepEnv = withProfileEnv(aiStepEnv(resolveEnv(), repoDir, executionManifest), provider, profileName);
|
|
479
|
-
const core = coreRun ? (0, core_execution_1.prepareCoreExecution)({ run: coreRun, cwd, repoDir, manifest: executionManifest, env: baseStepEnv }) : undefined;
|
|
539
|
+
const core = coreRun ? (0, core_execution_1.prepareCoreExecution)({ run: coreRun, cwd, repoDir, manifest: executionManifest, env: baseStepEnv, sourcePath: executionManifest ? undefined : opts.sourcePath?.() }) : undefined;
|
|
480
540
|
const stepEnv = core?.env ?? baseStepEnv;
|
|
481
541
|
const extraArgs = aiStepExtraArgs(adapter, cwd, repoDir, executionManifest);
|
|
482
542
|
if (repoDir) {
|
|
@@ -532,7 +592,7 @@ function createLoopExecutors(opts = {}) {
|
|
|
532
592
|
const baseEnv = resolveEnv();
|
|
533
593
|
const programmatic = existsRuntimeRequest(runtimeContextPath(cwd, coreRun.runId, baseEnv));
|
|
534
594
|
const baseStepEnv = programmatic ? programmaticStepEnv(baseEnv, repoDir, executionManifest) : withProfileEnv(aiStepEnv(baseEnv, repoDir, executionManifest), provider, profileName);
|
|
535
|
-
const core = (0, core_execution_1.prepareCoreExecution)({ run: coreRun, cwd, repoDir, manifest: executionManifest, env: baseStepEnv });
|
|
595
|
+
const core = (0, core_execution_1.prepareCoreExecution)({ run: coreRun, cwd, repoDir, manifest: executionManifest, env: baseStepEnv, sourcePath: executionManifest ? undefined : opts.sourcePath?.() });
|
|
536
596
|
if (existsRuntimeRequest(core.contextPath))
|
|
537
597
|
return (0, core_execution_1.checkCoreCompletion)(core.contextPath, cwd, { ...programmaticStepEnv(resolveEnv(), repoDir, executionManifest), SPECRAILS_EXECUTION_CONTEXT: core.contextPath }, coreRun.runId);
|
|
538
598
|
const env = (0, runtime_1.buildProviderEnv)((0, providers_1.getAdapter)(provider), { prompt: '', model, reasoning_effort: effort }, core.env);
|
|
@@ -1,10 +1,26 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.FACTORY_LOOPS = exports.FACTORY_REVISION_LOOP_ID = void 0;
|
|
4
|
+
exports.factoryLoopsForCapabilities = factoryLoopsForCapabilities;
|
|
4
5
|
exports.getFactoryLoop = getFactoryLoop;
|
|
5
6
|
exports.isFactoryLoopId = isFactoryLoopId;
|
|
6
7
|
exports.factoryLoopMode = factoryLoopMode;
|
|
7
8
|
exports.factoryLoopForMode = factoryLoopForMode;
|
|
9
|
+
/**
|
|
10
|
+
* Built-in "factory" loops — the app-owned loops that replace the old rail modes
|
|
11
|
+
* (`implement` / Freestyle's `freestyle` mode). These are the code DEFAULTS;
|
|
12
|
+
* `builtin-loops.ts` seeds them as editable loop rows (same ids) and resolves
|
|
13
|
+
* launches to an edited, published row when one exists.
|
|
14
|
+
*
|
|
15
|
+
* Each carries the canonical rail `mode` it maps to. The rail launch routes a
|
|
16
|
+
* factory loop to the matching engine via that mode (QueueManager slash command /
|
|
17
|
+
* Freestyle prompt) — the graph here is the faithful representation used for the
|
|
18
|
+
* gallery preview and as the seed when forked into a custom loop.
|
|
19
|
+
*
|
|
20
|
+
* IDs are namespaced `factory:<mode-ish>` so they never collide with user loop ids
|
|
21
|
+
* (which are random) and the rail resolver can recognise a factory loop.
|
|
22
|
+
*/
|
|
23
|
+
const loop_core_factory_1 = require("./loop-core-factory");
|
|
8
24
|
const loop_templates_1 = require("./loop-templates");
|
|
9
25
|
const GREEN_GOAL = 'Stop only when the latest verification step reports {{const:VERIFICATION_PASS}} and the history proves every acceptance criterion is implemented across all required tickets and repositories, with the relevant behavioral checks and configured tests/build passing. Green baseline checks, setup, or planning alone do not prove completion.';
|
|
10
26
|
// Factory loops run the WHOLE architect→developer→reviewer pipeline inside a
|
|
@@ -48,17 +64,10 @@ exports.FACTORY_LOOPS = [
|
|
|
48
64
|
{
|
|
49
65
|
id: 'factory:implement',
|
|
50
66
|
name: 'Implement',
|
|
51
|
-
description: '
|
|
67
|
+
description: 'Implements every spec on the rail in one run: plan, implement, review and verify, fixing failures until the checks pass.',
|
|
52
68
|
mode: 'implement',
|
|
53
69
|
graph: coreImplementationGraph('implement'),
|
|
54
70
|
},
|
|
55
|
-
{
|
|
56
|
-
id: 'factory:batch',
|
|
57
|
-
name: 'Batch Implement',
|
|
58
|
-
description: 'Run the Core batch implementation pipeline for all the rail\'s tickets. Core owns implementation, review, verification and corrections across the batch.',
|
|
59
|
-
mode: 'batch-implement',
|
|
60
|
-
graph: coreImplementationGraph('batch'),
|
|
61
|
-
},
|
|
62
71
|
{
|
|
63
72
|
id: 'factory:freestyle',
|
|
64
73
|
// User-facing name for the canonical freestyle mode: it hands the spec straight to the model
|
|
@@ -73,6 +82,8 @@ exports.FACTORY_LOOPS = [
|
|
|
73
82
|
SDD_QUICK_OPENSPEC_FACTORY,
|
|
74
83
|
];
|
|
75
84
|
const FACTORY_ALIASES = new Map([
|
|
85
|
+
// Batch was removed: `implement` already runs every rail ticket in one aggregate run.
|
|
86
|
+
['factory:batch', exports.FACTORY_LOOPS.find(f => f.id === 'factory:implement')],
|
|
76
87
|
['factory:openspec', SDD_QUICK_OPENSPEC_FACTORY],
|
|
77
88
|
[exports.FACTORY_REVISION_LOOP_ID, SDD_QUICK_OPENSPEC_FACTORY],
|
|
78
89
|
]);
|
|
@@ -80,8 +91,16 @@ const FACTORY_BY_ID = new Map([
|
|
|
80
91
|
...exports.FACTORY_LOOPS.map((f) => [f.id, f]),
|
|
81
92
|
...FACTORY_ALIASES,
|
|
82
93
|
]);
|
|
83
|
-
function
|
|
84
|
-
|
|
94
|
+
function factoryLoopsForCapabilities(capabilities) {
|
|
95
|
+
if (capabilities?.engineV2 !== 1 || capabilities.workflowDefinitions !== 1)
|
|
96
|
+
return exports.FACTORY_LOOPS;
|
|
97
|
+
return exports.FACTORY_LOOPS.map(factory => ({ ...factory, graph: (0, loop_core_factory_1.coreFactoryGraph)(factory.id === 'factory:implement' ? 'implement' : factory.id === 'factory:freestyle' ? 'freestyle' : 'quick-sdd') }));
|
|
98
|
+
}
|
|
99
|
+
function getFactoryLoop(id, capabilities) {
|
|
100
|
+
const legacy = FACTORY_BY_ID.get(id);
|
|
101
|
+
if (!legacy)
|
|
102
|
+
return undefined;
|
|
103
|
+
return factoryLoopsForCapabilities(capabilities).find(factory => factory.id === legacy.id);
|
|
85
104
|
}
|
|
86
105
|
/** True for any `factory:*` loop id. */
|
|
87
106
|
function isFactoryLoopId(id) {
|
|
@@ -93,12 +112,13 @@ function factoryLoopMode(id) {
|
|
|
93
112
|
}
|
|
94
113
|
/** Map a legacy rail mode → the matching factory loop (so a legacy rail with a
|
|
95
114
|
* `mode` and no selected loop resolves to a factory loop on read). */
|
|
96
|
-
function factoryLoopForMode(mode) {
|
|
115
|
+
function factoryLoopForMode(mode, capabilities) {
|
|
97
116
|
if (mode === 'implement')
|
|
98
|
-
return
|
|
99
|
-
|
|
100
|
-
|
|
117
|
+
return getFactoryLoop('factory:implement', capabilities);
|
|
118
|
+
// Removed Batch mode: stored `batch-implement` / `batch` rails resolve to Implement.
|
|
119
|
+
if (mode === 'batch-implement' || mode === 'batch')
|
|
120
|
+
return getFactoryLoop('factory:implement', capabilities);
|
|
101
121
|
if (mode === 'freestyle')
|
|
102
|
-
return
|
|
122
|
+
return getFactoryLoop('factory:freestyle', capabilities);
|
|
103
123
|
return undefined;
|
|
104
124
|
}
|
|
@@ -10,6 +10,10 @@
|
|
|
10
10
|
* trivially unit-testable and reused verbatim by both the store and the engine.
|
|
11
11
|
*/
|
|
12
12
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.CORE_NODE_KINDS = void 0;
|
|
14
|
+
exports.isDefinitionReviewerPath = isDefinitionReviewerPath;
|
|
15
|
+
exports.isDefinitionGraph = isDefinitionGraph;
|
|
16
|
+
exports.assertDefinitionGraph = assertDefinitionGraph;
|
|
13
17
|
exports.assertLoopShellRepositoryScope = assertLoopShellRepositoryScope;
|
|
14
18
|
exports.assertLoopFailureRecovery = assertLoopFailureRecovery;
|
|
15
19
|
exports.emptyLoopGraph = emptyLoopGraph;
|
|
@@ -19,6 +23,35 @@ exports.findStartNode = findStartNode;
|
|
|
19
23
|
exports.successors = successors;
|
|
20
24
|
exports.loopNeedsTicket = loopNeedsTicket;
|
|
21
25
|
exports.interpolateSpec = interpolateSpec;
|
|
26
|
+
exports.CORE_NODE_KINDS = ['prompt', 'role-turn', 'decider', 'condition', 'assign', 'verify', 'shell',
|
|
27
|
+
'openspec-validate', 'openspec-archive', 'approval', 'question', 'gate', 'map', 'join',
|
|
28
|
+
'component', 'implementation', 'end'];
|
|
29
|
+
function isDefinitionReviewerPath(graph, value) {
|
|
30
|
+
if (typeof value !== 'string' || !value || value.length > 2048)
|
|
31
|
+
return false;
|
|
32
|
+
const segments = value.split('/');
|
|
33
|
+
if (segments.length > 32)
|
|
34
|
+
return false;
|
|
35
|
+
let body = graph;
|
|
36
|
+
for (let index = 0; index < segments.length; index++) {
|
|
37
|
+
const node = body?.nodes.find(item => item.id === segments[index]);
|
|
38
|
+
if (!node || node.type !== 'core')
|
|
39
|
+
return false;
|
|
40
|
+
if (index === segments.length - 1)
|
|
41
|
+
return node.data?.kind === 'role-turn';
|
|
42
|
+
const ref = node.data?.kind === 'component' ? node.data.params?.ref : node.data?.kind === 'map' ? node.data.params?.body : undefined;
|
|
43
|
+
body = typeof ref === 'string' ? graph.components?.[ref] : undefined;
|
|
44
|
+
}
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
function isDefinitionGraph(graph) {
|
|
48
|
+
return graph.nodes.some(node => node.type === 'core');
|
|
49
|
+
}
|
|
50
|
+
function assertDefinitionGraph(graph) {
|
|
51
|
+
if (!isDefinitionGraph(graph) || graph.nodes.some(node => !['start', 'core', 'end'].includes(node.type))) {
|
|
52
|
+
throw new Error('A Core workflow must contain Core pieces and cannot mix Desktop execution steps.');
|
|
53
|
+
}
|
|
54
|
+
}
|
|
22
55
|
/** A named shell target must never silently fall back to another checkout. */
|
|
23
56
|
function assertLoopShellRepositoryScope(graph, repositoryIds) {
|
|
24
57
|
for (const node of graph.nodes) {
|
|
@@ -61,20 +94,33 @@ function emptyLoopGraph() {
|
|
|
61
94
|
* Returns all errors found (not just the first) so the builder can highlight
|
|
62
95
|
* every problem at once.
|
|
63
96
|
*/
|
|
64
|
-
function validateLoopGraph(graph) {
|
|
97
|
+
function validateLoopGraph(graph, catalog) {
|
|
65
98
|
const errors = [];
|
|
66
99
|
if (!graph || !Array.isArray(graph.nodes) || !Array.isArray(graph.edges)) {
|
|
67
100
|
return { valid: false, errors: [{ code: 'INVALID_CONFIG', message: 'A loop graph must contain nodes and edges arrays.' }] };
|
|
68
101
|
}
|
|
69
102
|
const nodes = graph.nodes;
|
|
70
103
|
const edges = graph.edges;
|
|
71
|
-
const kinds = new Set(['start', 'ai-step', 'shell', 'decider', 'condition', 'end']);
|
|
104
|
+
const kinds = new Set(['start', 'ai-step', 'shell', 'decider', 'condition', 'core', 'end']);
|
|
72
105
|
if (nodes.some((node) => !node || typeof node.id !== 'string' || !node.id.trim() || !kinds.has(node.type))) {
|
|
73
106
|
return { valid: false, errors: [{ code: 'INVALID_NODE', message: 'Every node needs a non-empty id and a supported node type.' }] };
|
|
74
107
|
}
|
|
75
108
|
if (edges.some((edge) => !edge || typeof edge.id !== 'string' || !edge.id.trim() || typeof edge.source !== 'string' || typeof edge.target !== 'string')) {
|
|
76
109
|
return { valid: false, errors: [{ code: 'DANGLING_EDGE', message: 'Every edge needs a non-empty id, source and target.' }] };
|
|
77
110
|
}
|
|
111
|
+
const definition = isDefinitionGraph(graph);
|
|
112
|
+
if (definition && nodes.some(node => !['start', 'core', 'end'].includes(node.type))) {
|
|
113
|
+
errors.push({ code: 'MIXED_ENGINES', message: 'Core workflows cannot mix Core pieces with Desktop execution steps.' });
|
|
114
|
+
}
|
|
115
|
+
for (const node of nodes) {
|
|
116
|
+
if (definition && (!/^[A-Za-z0-9][A-Za-z0-9_-]{0,119}$/.test(node.id) || ['START', 'END', '__start__', '__end__', 'next'].includes(node.id))) {
|
|
117
|
+
errors.push({ code: 'INVALID_NODE', nodeId: node.id, message: 'Core node IDs must be safe, non-reserved identifiers.' });
|
|
118
|
+
}
|
|
119
|
+
if (node.type === 'core' && (!exports.CORE_NODE_KINDS.includes(node.data?.kind) || !node.data?.params ||
|
|
120
|
+
typeof node.data.params !== 'object' || Array.isArray(node.data.params))) {
|
|
121
|
+
errors.push({ code: 'INVALID_NODE', nodeId: node.id, message: 'A Core piece needs a supported kind and an object of parameters.' });
|
|
122
|
+
}
|
|
123
|
+
}
|
|
78
124
|
for (const [items, code] of [[nodes, 'DUPLICATE_NODE'], [edges, 'DUPLICATE_EDGE']]) {
|
|
79
125
|
const ids = new Set();
|
|
80
126
|
for (const item of items) {
|
|
@@ -110,11 +156,16 @@ function validateLoopGraph(graph) {
|
|
|
110
156
|
}
|
|
111
157
|
}
|
|
112
158
|
// ── End presence ────────────────────────────────────────────────────────────
|
|
113
|
-
if (!nodes.some((n) => n.type === 'end')) {
|
|
159
|
+
if (!nodes.some((n) => n.type === 'end' || n.type === 'core' && n.data?.kind === 'end')) {
|
|
114
160
|
errors.push({ code: 'NO_END', message: 'The loop must have at least one End node.' });
|
|
115
161
|
}
|
|
116
162
|
// ── Config sanity ─────────────────────────────────────────────────────────────
|
|
117
163
|
const cfg = graph.config;
|
|
164
|
+
if (cfg?.legacyDeciderRole !== undefined && (!definition ||
|
|
165
|
+
typeof cfg.legacyDeciderRole !== 'string' || !/^[a-z][a-z0-9-]{0,63}$/.test(cfg.legacyDeciderRole) ||
|
|
166
|
+
!nodes.some(node => node.type === 'core' && node.data?.kind === 'decider' && node.data.params?.roleId === cfg.legacyDeciderRole))) {
|
|
167
|
+
errors.push({ code: 'INVALID_CONFIG', message: 'The legacy decision binding must identify a declared Core decider role.' });
|
|
168
|
+
}
|
|
118
169
|
if (!cfg ||
|
|
119
170
|
!Number.isInteger(cfg.maxIterations) ||
|
|
120
171
|
cfg.maxIterations < 1 ||
|
|
@@ -122,10 +173,13 @@ function validateLoopGraph(graph) {
|
|
|
122
173
|
!Number.isFinite(cfg.timeoutMinutes) ||
|
|
123
174
|
cfg.timeoutMinutes < 0 ||
|
|
124
175
|
(cfg.aiStepTimeoutMinutes !== undefined && (!Number.isFinite(cfg.aiStepTimeoutMinutes) || cfg.aiStepTimeoutMinutes < 0)) ||
|
|
125
|
-
(cfg.maxCostUsd !== undefined && !Number.isFinite(cfg.maxCostUsd))
|
|
176
|
+
(cfg.maxCostUsd !== undefined && !Number.isFinite(cfg.maxCostUsd)) ||
|
|
177
|
+
(cfg.maxTransitions !== undefined && (!Number.isInteger(cfg.maxTransitions) || cfg.maxTransitions < 1 || cfg.maxTransitions > 10000)) ||
|
|
178
|
+
(cfg.maxTokens !== undefined && (!Number.isInteger(cfg.maxTokens) || cfg.maxTokens < 1)) ||
|
|
179
|
+
(cfg.reviewerStepId !== undefined && !isDefinitionReviewerPath(graph, cfg.reviewerStepId))) {
|
|
126
180
|
errors.push({
|
|
127
181
|
code: 'INVALID_CONFIG',
|
|
128
|
-
message: 'maxIterations must be ≥ 1; timeouts must be finite and ≥ 0 (0 = no timeout); maxCostUsd must be finite.',
|
|
182
|
+
message: 'maxIterations must be ≥ 1; timeouts must be finite and ≥ 0 (0 = no timeout); maxCostUsd must be finite; reviewerStepId must identify a Core role-turn.',
|
|
129
183
|
});
|
|
130
184
|
}
|
|
131
185
|
// ── Dangling edges ────────────────────────────────────────────────────────────
|
|
@@ -143,10 +197,26 @@ function validateLoopGraph(graph) {
|
|
|
143
197
|
// shell or condition node would silently skip all but its first branch.
|
|
144
198
|
for (const node of nodes) {
|
|
145
199
|
const out = edges.filter((edge) => edge.source === node.id);
|
|
146
|
-
if (node.type === 'end') {
|
|
200
|
+
if (node.type === 'end' || node.type === 'core' && node.data?.kind === 'end') {
|
|
147
201
|
if (out.length)
|
|
148
202
|
errors.push({ code: 'INVALID_BRANCH', nodeId: node.id, message: 'End nodes cannot have outgoing edges.' });
|
|
149
203
|
}
|
|
204
|
+
else if (node.type === 'core') {
|
|
205
|
+
const descriptor = catalog?.find(piece => piece.kind === node.data?.kind);
|
|
206
|
+
if (catalog && !descriptor)
|
|
207
|
+
errors.push({ code: 'INVALID_NODE', nodeId: node.id, message: 'The installed Core does not advertise this piece.' });
|
|
208
|
+
if (!out.length)
|
|
209
|
+
errors.push({ code: 'DEAD_END', nodeId: node.id, message: 'A Core piece needs labeled outcome edges.' });
|
|
210
|
+
const labels = new Set();
|
|
211
|
+
for (const edge of out) {
|
|
212
|
+
if (typeof edge.label !== 'string' || !/^[a-z][a-z0-9-]{0,31}$/.test(edge.label) || labels.has(edge.label) ||
|
|
213
|
+
(descriptor && !descriptor.outcomes.includes(edge.label))) {
|
|
214
|
+
errors.push({ code: 'INVALID_BRANCH', nodeId: node.id, edgeId: edge.id, message: 'Core outcome edges need unique labels advertised by the piece.' });
|
|
215
|
+
}
|
|
216
|
+
if (edge.label)
|
|
217
|
+
labels.add(edge.label);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
150
220
|
else if (node.type === 'decider') {
|
|
151
221
|
const labeled = out.some((edge) => edge.branch !== undefined);
|
|
152
222
|
const validBranches = labeled
|
|
@@ -218,7 +288,8 @@ function successors(graph, nodeId) {
|
|
|
218
288
|
.filter((n) => n !== undefined);
|
|
219
289
|
}
|
|
220
290
|
/** Whether a loop "needs a ticket/spec" — it references a `{{spec.*}}` token or a
|
|
221
|
-
* ticket-consuming command (`{{cmd:implement|
|
|
291
|
+
* ticket-consuming command (`{{cmd:implement|freestyle}}`, plus the legacy
|
|
292
|
+
* `{{cmd:batch}}` alias of implement). Ticket-needing
|
|
222
293
|
* loops belong on a rail (the rail provides the spec). Ticket-LESS loops are
|
|
223
294
|
* standalone (repo-wide CI watch, lint, audit…) and run from the Loops page's
|
|
224
295
|
* "Run" action, NOT a rail — launching one on a rail would just re-run the same
|
|
@@ -229,12 +300,17 @@ function loopNeedsTicket(graph) {
|
|
|
229
300
|
if (!graph)
|
|
230
301
|
return false;
|
|
231
302
|
for (const node of graph.nodes) {
|
|
232
|
-
|
|
303
|
+
if (node.type === 'core' && node.data?.kind === 'implementation')
|
|
304
|
+
return true;
|
|
305
|
+
const text = [node.data?.prompt, node.data?.command, node.data?.goal,
|
|
306
|
+
node.type === 'core' ? JSON.stringify(node.data?.params) : undefined]
|
|
233
307
|
.filter((v) => typeof v === 'string')
|
|
234
308
|
.join('\n');
|
|
235
309
|
if (SPEC_TOKEN_RE.test(text) || TICKET_CMD_RE.test(text))
|
|
236
310
|
return true;
|
|
237
311
|
}
|
|
312
|
+
if (Object.values(graph.components ?? {}).some(component => loopNeedsTicket(component)))
|
|
313
|
+
return true;
|
|
238
314
|
return false;
|
|
239
315
|
}
|
|
240
316
|
function interpolateSpec(text, spec) {
|