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
|
@@ -4,17 +4,51 @@ exports.registerLoopsRoutes = registerLoopsRoutes;
|
|
|
4
4
|
const ids_1 = require("../../../ids");
|
|
5
5
|
const feature_flags_1 = require("../../../feature-flags");
|
|
6
6
|
const loops_store_1 = require("./loops-store");
|
|
7
|
+
const loop_graph_1 = require("./loop-graph");
|
|
8
|
+
const loop_definition_1 = require("./loop-definition");
|
|
9
|
+
const loop_compat_1 = require("./loop-compat");
|
|
10
|
+
const loop_migration_1 = require("./loop-migration");
|
|
11
|
+
const agent_runtime_loader_1 = require("../../agent-runtime/runtime/agent-runtime-loader");
|
|
7
12
|
const loop_templates_1 = require("./loop-templates");
|
|
8
13
|
const loop_factory_1 = require("./loop-factory");
|
|
14
|
+
const builtin_loops_1 = require("./builtin-loops");
|
|
9
15
|
const loop_command_catalog_1 = require("./loop-command-catalog");
|
|
10
16
|
const loop_constants_1 = require("./loop-constants");
|
|
11
17
|
const loop_preview_1 = require("./loop-preview");
|
|
18
|
+
const BUILTIN_DELETE_MESSAGE = 'Built-in loops cannot be deleted. Use Restore original to reset it, or Duplicate it to keep a separate copy.';
|
|
19
|
+
async function factoryCapabilities() {
|
|
20
|
+
try {
|
|
21
|
+
return (await (0, agent_runtime_loader_1.loadCoreAgentRuntime)()).api?.capabilities;
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
12
27
|
function isNonEmptyString(v) {
|
|
13
28
|
return typeof v === 'string' && v.trim().length > 0;
|
|
14
29
|
}
|
|
15
30
|
function registerLoopsRoutes(router, deps) {
|
|
16
31
|
const { db } = deps;
|
|
17
32
|
const isRunning = deps.isLoopRunning ?? (() => false);
|
|
33
|
+
const isBuiltin = (id) => Boolean((0, loops_store_1.getLoop)(db, id)?.builtinId);
|
|
34
|
+
// One seeding pass at a time; later calls re-check (Core may have appeared or
|
|
35
|
+
// changed) and are cheap because the Core API probe is cached by fingerprint.
|
|
36
|
+
let seeding = null;
|
|
37
|
+
function seedBuiltins() {
|
|
38
|
+
if (!deps.builtinLoops)
|
|
39
|
+
return Promise.resolve();
|
|
40
|
+
if (!seeding) {
|
|
41
|
+
seeding = (0, builtin_loops_1.ensureBuiltinLoops)(db, deps.builtinLoops)
|
|
42
|
+
.catch((error) => { console.warn('[loops] built-in loop seeding failed:', error instanceof Error ? error.message : error); })
|
|
43
|
+
.finally(() => { seeding = null; });
|
|
44
|
+
}
|
|
45
|
+
return seeding;
|
|
46
|
+
}
|
|
47
|
+
// Startup: the registry (and its desktop DB) exists when routes are registered.
|
|
48
|
+
// The Core API probe is synchronous on first use, so defer it past startup
|
|
49
|
+
// (and the listen call) instead of delaying the server by the probe timeout.
|
|
50
|
+
if ((0, feature_flags_1.isLoopsEnabled)() && deps.builtinLoops)
|
|
51
|
+
setImmediate(() => { void seedBuiltins(); }).unref?.();
|
|
18
52
|
// Single gate: every loops route 404s when the feature is disabled.
|
|
19
53
|
function guard(res) {
|
|
20
54
|
if (!(0, feature_flags_1.isLoopsEnabled)()) {
|
|
@@ -24,11 +58,11 @@ function registerLoopsRoutes(router, deps) {
|
|
|
24
58
|
return true;
|
|
25
59
|
}
|
|
26
60
|
// ── Templates ──────────────────────────────────────────────────────────────
|
|
27
|
-
router.get('/loop-templates', (_req, res) => {
|
|
61
|
+
router.get('/loop-templates', async (_req, res) => {
|
|
28
62
|
if (!guard(res))
|
|
29
63
|
return;
|
|
30
64
|
res.json({
|
|
31
|
-
templates: loop_templates_1.
|
|
65
|
+
templates: (0, loop_templates_1.loopTemplatesForCapabilities)(await factoryCapabilities()).map((t) => ({
|
|
32
66
|
id: t.id,
|
|
33
67
|
name: t.name,
|
|
34
68
|
description: t.description,
|
|
@@ -41,9 +75,10 @@ function registerLoopsRoutes(router, deps) {
|
|
|
41
75
|
});
|
|
42
76
|
});
|
|
43
77
|
// ── List / get ───────────────────────────────────────────────────────────────
|
|
44
|
-
router.get('/loops', (_req, res) => {
|
|
78
|
+
router.get('/loops', async (_req, res) => {
|
|
45
79
|
if (!guard(res))
|
|
46
80
|
return;
|
|
81
|
+
await seedBuiltins();
|
|
47
82
|
res.json({ loops: (0, loops_store_1.listLoops)(db) });
|
|
48
83
|
});
|
|
49
84
|
// Magic-command catalog for the builder palette (drives the AI Step chips).
|
|
@@ -53,36 +88,65 @@ function registerLoopsRoutes(router, deps) {
|
|
|
53
88
|
return;
|
|
54
89
|
res.json({ commands: loop_command_catalog_1.LOOP_COMMANDS.map((c) => ({ name: c.name, label: c.label, description: c.description })) });
|
|
55
90
|
});
|
|
56
|
-
//
|
|
57
|
-
|
|
58
|
-
|
|
91
|
+
// Catalog belongs to the installed runtime; global authoring has no project state.
|
|
92
|
+
router.get('/loops/catalog', async (_req, res) => {
|
|
93
|
+
if (!guard(res))
|
|
94
|
+
return;
|
|
95
|
+
try {
|
|
96
|
+
const runtime = await (0, agent_runtime_loader_1.loadCoreAgentRuntime)();
|
|
97
|
+
if (runtime.api?.capabilities?.engineV2 !== 1 || !runtime.listWorkflows) {
|
|
98
|
+
res.status(409).json({ error: 'engine_unsupported', message: 'Update Core to author executable workflows.' });
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
res.json(runtime.listWorkflows());
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
res.status(503).json({ error: 'runtime_catalog_unavailable', message: error instanceof Error ? error.message : 'Core catalog unavailable' });
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
// ── Factory loops (built-ins) ────────────────────────────────────────────────
|
|
108
|
+
// Registered BEFORE `/loops/:id` so "factory" is not captured as an id. Kept
|
|
109
|
+
// for companion/mobile/MCP back-compat: each entry reflects its editable row
|
|
110
|
+
// when seeded (name, description and the graph a rail launch would run).
|
|
111
|
+
router.get('/loops/factory', async (_req, res) => {
|
|
59
112
|
if (!guard(res))
|
|
60
113
|
return;
|
|
114
|
+
await seedBuiltins();
|
|
115
|
+
const capabilities = await factoryCapabilities();
|
|
61
116
|
res.json({
|
|
62
|
-
factoryLoops: loop_factory_1.
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
117
|
+
factoryLoops: (0, loop_factory_1.factoryLoopsForCapabilities)(capabilities).map((f) => {
|
|
118
|
+
const resolved = (0, builtin_loops_1.resolveBuiltinLoop)(db, f.id, capabilities);
|
|
119
|
+
return {
|
|
120
|
+
id: f.id,
|
|
121
|
+
name: resolved?.name ?? f.name,
|
|
122
|
+
description: resolved?.description ?? f.description,
|
|
123
|
+
mode: f.mode,
|
|
124
|
+
requiredCapability: f.requiredCapability ?? null,
|
|
125
|
+
// Absent means launchable (every pre-existing factory loop is).
|
|
126
|
+
launchable: f.launchable !== false,
|
|
127
|
+
graph: resolved?.graph ?? f.graph,
|
|
128
|
+
// Seeded built-ins are ordinary loops: edit them via PUT /loops/:id.
|
|
129
|
+
editable: Boolean(resolved?.row),
|
|
130
|
+
status: resolved?.row?.status ?? 'published',
|
|
131
|
+
modified: resolved?.row?.builtinModified ?? false,
|
|
132
|
+
};
|
|
133
|
+
}),
|
|
72
134
|
});
|
|
73
135
|
});
|
|
74
|
-
//
|
|
75
|
-
|
|
136
|
+
// Back-compat alias of `POST /loops/:id/duplicate` for a built-in: creates a
|
|
137
|
+
// separate Draft from the built-in's current content (the built-in is unchanged).
|
|
138
|
+
router.post('/loops/factory/:id/fork', async (req, res) => {
|
|
76
139
|
if (!guard(res))
|
|
77
140
|
return;
|
|
78
|
-
const
|
|
79
|
-
if (!
|
|
141
|
+
const resolved = (0, builtin_loops_1.resolveBuiltinLoop)(db, req.params.id, await factoryCapabilities());
|
|
142
|
+
if (!resolved) {
|
|
80
143
|
res.status(404).json({ error: 'Factory loop not found' });
|
|
81
144
|
return;
|
|
82
145
|
}
|
|
83
146
|
const body = req.body ?? {};
|
|
84
|
-
const name = isNonEmptyString(body.name) ? body.name.trim() : `${
|
|
85
|
-
const
|
|
147
|
+
const name = isNonEmptyString(body.name) ? body.name.trim() : `${resolved.name} (fork)`;
|
|
148
|
+
const source = resolved.row ?? { description: resolved.description, graph: resolved.graph };
|
|
149
|
+
const loop = (0, loops_store_1.createLoop)(db, { id: (0, ids_1.newId)(), name, description: source.description, graph: source.graph });
|
|
86
150
|
res.status(201).json({ loop });
|
|
87
151
|
});
|
|
88
152
|
// ── Constants library (global, cross-loop) ─────────────────────────────────
|
|
@@ -144,7 +208,12 @@ function registerLoopsRoutes(router, deps) {
|
|
|
144
208
|
return;
|
|
145
209
|
}
|
|
146
210
|
const provider = typeof body.provider === 'string' ? body.provider : 'claude';
|
|
147
|
-
|
|
211
|
+
try {
|
|
212
|
+
res.json((0, loop_preview_1.previewLoop)(body.graph, { provider, constants: (0, loop_constants_1.loadConstantMap)(db) }));
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
res.status(400).json({ errors: [{ code: 'definition_invalid', message: error instanceof Error ? error.message : 'Invalid workflow' }] });
|
|
216
|
+
}
|
|
148
217
|
});
|
|
149
218
|
// Import loops from an export envelope. Duplicate NAMES are skipped (returned
|
|
150
219
|
// in `skipped`); the rest land as fresh Drafts. Registered BEFORE `/loops/:id`.
|
|
@@ -158,6 +227,42 @@ function registerLoopsRoutes(router, deps) {
|
|
|
158
227
|
}
|
|
159
228
|
res.json((0, loops_store_1.importLoops)(db, body.loops, ids_1.newId));
|
|
160
229
|
});
|
|
230
|
+
// Read-only migration assessment; registered before `/loops/:id`. It never
|
|
231
|
+
// converts or (un)publishes: conversion stays an explicit, reviewable step.
|
|
232
|
+
router.get('/loops/migration', async (_req, res) => {
|
|
233
|
+
if (!guard(res))
|
|
234
|
+
return;
|
|
235
|
+
try {
|
|
236
|
+
const runtime = await (0, agent_runtime_loader_1.loadCoreAgentRuntime)();
|
|
237
|
+
const catalog = runtime.listWorkflows?.();
|
|
238
|
+
if (runtime.api?.capabilities?.engineV2 !== 1 || runtime.api?.capabilities?.workflowDefinitions !== 1 || !catalog || catalog.nodeKindsVersion < 5) {
|
|
239
|
+
res.status(409).json({ error: 'engine_unsupported', message: 'Migration assessment requires Core catalog version 5 or later.' });
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
const constants = (0, loop_constants_1.loadConstantMap)(db);
|
|
243
|
+
res.json((0, loop_migration_1.assessLoopMigration)((0, loops_store_1.listLoops)(db), {
|
|
244
|
+
isRunning,
|
|
245
|
+
validate: (loop, graph) => {
|
|
246
|
+
const definition = (0, loop_definition_1.compileLoopToDefinition)(graph, { id: loop.id, title: loop.name, constants, provider: 'claude', spec: { id: 1, title: 'Sample spec', description: 'Migration assessment' } });
|
|
247
|
+
const validation = runtime.validateWorkflowDefinition(definition, { structural: true });
|
|
248
|
+
return validation.ok ? { ok: true } : { ok: false, errors: validation.errors.map(error => ({ ...error, nodeId: error.nodeId ?? error.path?.match(/^\/nodes\/([^/]+)/)?.[1] })) };
|
|
249
|
+
},
|
|
250
|
+
}));
|
|
251
|
+
}
|
|
252
|
+
catch (error) {
|
|
253
|
+
res.status(503).json({ error: 'runtime_validation_unavailable', message: error instanceof Error ? error.message : 'Core validation unavailable' });
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
router.get('/loops/:id/legacy-graph', (req, res) => {
|
|
257
|
+
if (!guard(res))
|
|
258
|
+
return;
|
|
259
|
+
const backup = (0, loops_store_1.readLegacyLoopGraph)(db, req.params.id);
|
|
260
|
+
if (!backup) {
|
|
261
|
+
res.status(404).json({ error: 'Original legacy graph not found' });
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
res.json(backup);
|
|
265
|
+
});
|
|
161
266
|
router.get('/loops/:id', (req, res) => {
|
|
162
267
|
if (!guard(res))
|
|
163
268
|
return;
|
|
@@ -186,10 +291,10 @@ function registerLoopsRoutes(router, deps) {
|
|
|
186
291
|
res.status(201).json({ loop });
|
|
187
292
|
});
|
|
188
293
|
// ── Instantiate from a template ───────────────────────────────────────────────
|
|
189
|
-
router.post('/loops/from-template/:templateId', (req, res) => {
|
|
294
|
+
router.post('/loops/from-template/:templateId', async (req, res) => {
|
|
190
295
|
if (!guard(res))
|
|
191
296
|
return;
|
|
192
|
-
const template = (0, loop_templates_1.getLoopTemplate)(req.params.templateId);
|
|
297
|
+
const template = (0, loop_templates_1.getLoopTemplate)(req.params.templateId, await factoryCapabilities());
|
|
193
298
|
if (!template) {
|
|
194
299
|
res.status(404).json({ error: 'Template not found' });
|
|
195
300
|
return;
|
|
@@ -213,7 +318,10 @@ function registerLoopsRoutes(router, deps) {
|
|
|
213
318
|
res.status(404).json({ error: 'Loop not found' });
|
|
214
319
|
return;
|
|
215
320
|
}
|
|
216
|
-
|
|
321
|
+
// Runs freeze a clone of their graph at launch, and built-in launches use
|
|
322
|
+
// the last Published snapshot while an edit is Draft, so a running built-in
|
|
323
|
+
// stays editable. Other loops keep the conservative running guard.
|
|
324
|
+
if (!isBuiltin(id) && isRunning(id)) {
|
|
217
325
|
res.status(409).json({ error: 'Loop is running; stop the run before editing' });
|
|
218
326
|
return;
|
|
219
327
|
}
|
|
@@ -234,7 +342,59 @@ function registerLoopsRoutes(router, deps) {
|
|
|
234
342
|
res.json({ loop });
|
|
235
343
|
});
|
|
236
344
|
// ── Publish / unpublish ───────────────────────────────────────────────────────
|
|
237
|
-
router.post('/loops/:id/
|
|
345
|
+
router.post('/loops/:id/convert', async (req, res) => {
|
|
346
|
+
if (!guard(res))
|
|
347
|
+
return;
|
|
348
|
+
const id = req.params.id, current = (0, loops_store_1.getLoop)(db, id);
|
|
349
|
+
if (!current) {
|
|
350
|
+
res.status(404).json({ error: 'Loop not found' });
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
if (isRunning(id)) {
|
|
354
|
+
res.status(409).json({ error: 'loop_running' });
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
if ((0, loop_graph_1.isDefinitionGraph)(current.graph) && (0, loops_store_1.readLegacyLoopGraph)(db, id)) {
|
|
358
|
+
res.json({ loop: current, nodeIds: {}, issues: [], alreadyConverted: true });
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
try {
|
|
362
|
+
const converted = (0, loop_compat_1.convertLegacyLoop)(current.graph, {
|
|
363
|
+
...(typeof req.body?.repositoryId === 'string' && req.body.repositoryId.trim() ? { repositoryId: req.body.repositoryId.trim() } : {}),
|
|
364
|
+
});
|
|
365
|
+
if (!converted.ok) {
|
|
366
|
+
res.status(422).json({ error: 'conversion_invalid', errors: converted.issues });
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
const runtime = await (0, agent_runtime_loader_1.loadCoreAgentRuntime)();
|
|
370
|
+
const catalog = runtime.listWorkflows?.();
|
|
371
|
+
if (runtime.api?.capabilities?.engineV2 !== 1 || runtime.api?.capabilities?.workflowDefinitions !== 1 ||
|
|
372
|
+
!catalog || catalog.nodeKindsVersion < 5) {
|
|
373
|
+
res.status(409).json({ error: 'engine_unsupported', message: 'Legacy conversion requires Core catalog version 5 or later.' });
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
const definition = (0, loop_definition_1.compileLoopToDefinition)(converted.graph, { id: current.id, title: current.name, constants: (0, loop_constants_1.loadConstantMap)(db), provider: 'claude', spec: { id: 1, title: 'Sample spec', description: 'Conversion preview' } });
|
|
377
|
+
const validation = runtime.validateWorkflowDefinition(definition, { structural: true });
|
|
378
|
+
if (!validation.ok) {
|
|
379
|
+
res.status(422).json({ error: 'conversion_invalid', errors: validation.errors });
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
if (isRunning(id)) {
|
|
383
|
+
res.status(409).json({ error: 'loop_running' });
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
const loop = (0, loops_store_1.updateLoop)(db, id, { graph: converted.graph }, current);
|
|
387
|
+
res.json({ loop, nodeIds: converted.nodeIds, issues: converted.issues });
|
|
388
|
+
}
|
|
389
|
+
catch (error) {
|
|
390
|
+
if (error instanceof loops_store_1.LoopPublicationConflict) {
|
|
391
|
+
res.status(409).json({ error: 'loop_changed' });
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
res.status(400).json({ error: 'conversion_invalid', message: error instanceof Error ? error.message : 'Conversion failed' });
|
|
395
|
+
}
|
|
396
|
+
});
|
|
397
|
+
router.post('/loops/:id/publish', async (req, res) => {
|
|
238
398
|
if (!guard(res))
|
|
239
399
|
return;
|
|
240
400
|
const id = req.params.id;
|
|
@@ -243,15 +403,40 @@ function registerLoopsRoutes(router, deps) {
|
|
|
243
403
|
return;
|
|
244
404
|
}
|
|
245
405
|
try {
|
|
246
|
-
const
|
|
406
|
+
const current = (0, loops_store_1.getLoop)(db, id);
|
|
407
|
+
if ((0, loop_graph_1.isDefinitionGraph)(current.graph)) {
|
|
408
|
+
const runtime = await (0, agent_runtime_loader_1.loadCoreAgentRuntime)();
|
|
409
|
+
if (runtime.api?.capabilities?.engineV2 !== 1 || runtime.api?.capabilities?.workflowDefinitions !== 1) {
|
|
410
|
+
res.status(409).json({ error: 'engine_unsupported', message: 'Update Core to publish executable workflows.' });
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
let draft;
|
|
414
|
+
try {
|
|
415
|
+
draft = (0, loop_definition_1.compileLoopToDefinition)(current.graph, { id: current.id, title: current.name, constants: (0, loop_constants_1.loadConstantMap)(db), provider: 'claude', spec: { id: 1, title: 'Sample spec', description: 'Publication preview' } });
|
|
416
|
+
}
|
|
417
|
+
catch (error) {
|
|
418
|
+
res.status(400).json({ errors: [{ code: 'definition_invalid', message: error instanceof Error ? error.message : 'Invalid workflow' }] });
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
const validation = runtime.validateWorkflowDefinition(draft, { structural: true });
|
|
422
|
+
if (!validation.ok) {
|
|
423
|
+
res.status(400).json({ errors: validation.errors.map(error => ({ ...error, nodeId: error.nodeId ?? error.path?.match(/^\/nodes\/([^/]+)/)?.[1] })) });
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
const loop = (0, loops_store_1.publishLoop)(db, id, current);
|
|
247
428
|
res.json({ loop });
|
|
248
429
|
}
|
|
249
430
|
catch (err) {
|
|
431
|
+
if (err instanceof loops_store_1.LoopPublicationConflict) {
|
|
432
|
+
res.status(409).json({ error: err.message, code: 'loop_changed' });
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
250
435
|
if (err instanceof loops_store_1.LoopValidationError) {
|
|
251
436
|
res.status(422).json({ error: 'Loop graph is invalid', errors: err.errors });
|
|
252
437
|
return;
|
|
253
438
|
}
|
|
254
|
-
|
|
439
|
+
res.status(503).json({ error: 'runtime_validation_unavailable', message: err instanceof Error ? err.message : 'Core validation unavailable' });
|
|
255
440
|
}
|
|
256
441
|
});
|
|
257
442
|
router.post('/loops/:id/unpublish', (req, res) => {
|
|
@@ -262,12 +447,40 @@ function registerLoopsRoutes(router, deps) {
|
|
|
262
447
|
res.status(404).json({ error: 'Loop not found' });
|
|
263
448
|
return;
|
|
264
449
|
}
|
|
450
|
+
if (isBuiltin(id)) {
|
|
451
|
+
res.status(409).json({ error: 'builtin_loop', message: 'Built-in loops stay available on rails. Edit and publish it, or use Restore original.' });
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
265
454
|
if (isRunning(id)) {
|
|
266
455
|
res.status(409).json({ error: 'Loop is running; stop the run first' });
|
|
267
456
|
return;
|
|
268
457
|
}
|
|
269
458
|
res.json({ loop: (0, loops_store_1.unpublishLoop)(db, id) });
|
|
270
459
|
});
|
|
460
|
+
// ── Restore a built-in to its current default ─────────────────────────────────
|
|
461
|
+
router.post('/loops/:id/restore-builtin', async (req, res) => {
|
|
462
|
+
if (!guard(res))
|
|
463
|
+
return;
|
|
464
|
+
const id = req.params.id;
|
|
465
|
+
if (!(0, loops_store_1.getLoop)(db, id)) {
|
|
466
|
+
res.status(404).json({ error: 'Loop not found' });
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
if (!isBuiltin(id)) {
|
|
470
|
+
res.status(400).json({ error: 'not_builtin', message: 'Only built-in loops can be restored.' });
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
if (isRunning(id)) {
|
|
474
|
+
res.status(409).json({ error: 'loop_running', message: 'Loop is running; stop the run before restoring it' });
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
const loop = await (0, builtin_loops_1.restoreBuiltin)(db, id, deps.builtinLoops);
|
|
478
|
+
if (!loop) {
|
|
479
|
+
res.status(404).json({ error: 'Built-in loop default not found' });
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
res.json({ loop });
|
|
483
|
+
});
|
|
271
484
|
// ── Duplicate ─────────────────────────────────────────────────────────────────
|
|
272
485
|
router.post('/loops/:id/duplicate', (req, res) => {
|
|
273
486
|
if (!guard(res))
|
|
@@ -292,6 +505,10 @@ function registerLoopsRoutes(router, deps) {
|
|
|
292
505
|
res.status(404).json({ error: 'Loop not found' });
|
|
293
506
|
return;
|
|
294
507
|
}
|
|
508
|
+
if (isBuiltin(id)) {
|
|
509
|
+
res.status(409).json({ error: 'builtin_loop', message: BUILTIN_DELETE_MESSAGE });
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
295
512
|
if (isRunning(id)) {
|
|
296
513
|
res.status(409).json({ error: 'Loop is running; stop the run before deleting' });
|
|
297
514
|
return;
|
|
@@ -1,15 +1,32 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.LoopValidationError = void 0;
|
|
3
|
+
exports.LoopPublicationConflict = exports.LoopValidationError = void 0;
|
|
4
|
+
exports.builtinContentHash = builtinContentHash;
|
|
4
5
|
exports.listLoops = listLoops;
|
|
5
6
|
exports.getLoop = getLoop;
|
|
6
7
|
exports.createLoop = createLoop;
|
|
7
8
|
exports.updateLoop = updateLoop;
|
|
9
|
+
exports.readLegacyLoopGraph = readLegacyLoopGraph;
|
|
8
10
|
exports.publishLoop = publishLoop;
|
|
9
11
|
exports.unpublishLoop = unpublishLoop;
|
|
10
12
|
exports.duplicateLoop = duplicateLoop;
|
|
11
13
|
exports.deleteLoop = deleteLoop;
|
|
12
14
|
exports.importLoops = importLoops;
|
|
15
|
+
exports.seedBuiltinLoops = seedBuiltinLoops;
|
|
16
|
+
exports.restoreBuiltinLoop = restoreBuiltinLoop;
|
|
17
|
+
exports.getBuiltinLoop = getBuiltinLoop;
|
|
18
|
+
exports.readPublishedLoopGraph = readPublishedLoopGraph;
|
|
19
|
+
/**
|
|
20
|
+
* Loops store — CRUD + Draft/Published lifecycle over the app-level `loops`
|
|
21
|
+
* table in `desktop.sqlite` (see desktop-db.ts migration 14).
|
|
22
|
+
*
|
|
23
|
+
* Definitions are GLOBAL (cross-project): a loop is a reusable recipe consumed
|
|
24
|
+
* from any project's rail. The third lifecycle state "Running" is NOT stored
|
|
25
|
+
* here — it is derived from active per-project `loop_runs` and enforced at the
|
|
26
|
+
* router/service layer (which can see the project registry); this store stays
|
|
27
|
+
* focused on the persistent definition + the Draft⇄Published transition.
|
|
28
|
+
*/
|
|
29
|
+
const crypto_1 = require("crypto");
|
|
13
30
|
const loop_graph_1 = require("./loop-graph");
|
|
14
31
|
/** Thrown by {@link publishLoop} when the graph fails validation. The router
|
|
15
32
|
* maps this to a 422 carrying the per-node errors. */
|
|
@@ -22,6 +39,11 @@ class LoopValidationError extends Error {
|
|
|
22
39
|
}
|
|
23
40
|
}
|
|
24
41
|
exports.LoopValidationError = LoopValidationError;
|
|
42
|
+
/** Validation cannot authorize publishing a different edit made while Core loaded. */
|
|
43
|
+
class LoopPublicationConflict extends Error {
|
|
44
|
+
constructor() { super('Loop changed during validation; reload and publish again'); this.name = 'LoopPublicationConflict'; }
|
|
45
|
+
}
|
|
46
|
+
exports.LoopPublicationConflict = LoopPublicationConflict;
|
|
25
47
|
function mapRow(raw) {
|
|
26
48
|
if (!raw)
|
|
27
49
|
return undefined;
|
|
@@ -40,13 +62,25 @@ function mapRow(raw) {
|
|
|
40
62
|
graph,
|
|
41
63
|
createdAt: raw.created_at,
|
|
42
64
|
updatedAt: raw.updated_at,
|
|
65
|
+
...(raw.has_legacy_graph ? { hasLegacyGraph: true } : {}),
|
|
66
|
+
...(raw.builtin_id ? {
|
|
67
|
+
builtinId: raw.builtin_id,
|
|
68
|
+
builtinModified: builtinContentHash({ name: raw.name, description: raw.description, graph }) !== raw.builtin_default_hash,
|
|
69
|
+
} : {}),
|
|
43
70
|
};
|
|
44
71
|
}
|
|
72
|
+
/** Content identity of a built-in row (name, description and graph). The
|
|
73
|
+
* stored hash is the default last seeded, so equality means "unedited". */
|
|
74
|
+
function builtinContentHash(content) {
|
|
75
|
+
return (0, crypto_1.createHash)('sha256').update(JSON.stringify([content.name, content.description, content.graph])).digest('hex');
|
|
76
|
+
}
|
|
77
|
+
// Backups can be large; load their contents only for an explicit export.
|
|
78
|
+
const loopColumns = 'id, name, description, status, graph, created_at, updated_at, (graph_legacy IS NOT NULL) AS has_legacy_graph, builtin_id, builtin_default_hash';
|
|
45
79
|
function listLoops(db) {
|
|
46
|
-
return db.prepare(
|
|
80
|
+
return db.prepare(`SELECT ${loopColumns} FROM loops ORDER BY updated_at DESC, created_at DESC`).all().map((r) => mapRow(r));
|
|
47
81
|
}
|
|
48
82
|
function getLoop(db, id) {
|
|
49
|
-
return mapRow(db.prepare(
|
|
83
|
+
return mapRow(db.prepare(`SELECT ${loopColumns} FROM loops WHERE id = ?`).get(id));
|
|
50
84
|
}
|
|
51
85
|
/** Create a new loop. Always starts as a Draft. `graph` defaults to an empty
|
|
52
86
|
* (structurally-invalid-to-publish) canvas the user fills in the builder. */
|
|
@@ -60,30 +94,56 @@ function createLoop(db, input) {
|
|
|
60
94
|
* Published loop un-publishes it — it must be re-validated and re-published
|
|
61
95
|
* before it is offered in the rail picker again).
|
|
62
96
|
*/
|
|
63
|
-
function updateLoop(db, id, patch) {
|
|
64
|
-
|
|
65
|
-
|
|
97
|
+
function updateLoop(db, id, patch, expected) {
|
|
98
|
+
return db.transaction(() => {
|
|
99
|
+
const existing = getLoop(db, id);
|
|
100
|
+
if (expected && (!existing || JSON.stringify([existing.name, existing.description, existing.graph, existing.status]) !==
|
|
101
|
+
JSON.stringify([expected.name, expected.description, expected.graph, expected.status])))
|
|
102
|
+
throw new LoopPublicationConflict();
|
|
103
|
+
if (!existing)
|
|
104
|
+
return undefined;
|
|
105
|
+
const name = patch.name ?? existing.name;
|
|
106
|
+
const description = patch.description !== undefined ? patch.description : existing.description;
|
|
107
|
+
const graph = patch.graph ?? existing.graph;
|
|
108
|
+
const converting = patch.graph !== undefined && Array.isArray(graph.nodes) && graph.nodes.some(node => node?.type === 'core') &&
|
|
109
|
+
Array.isArray(existing.graph.nodes) && existing.graph.nodes.some(node => ['ai-step', 'shell', 'decider', 'condition'].includes(node?.type));
|
|
110
|
+
// Both expressions read the old row. A failed save cannot leave a backup
|
|
111
|
+
// that claims a conversion happened, and retries cannot replace its bytes.
|
|
112
|
+
db.prepare(`UPDATE loops SET name = ?, description = ?,
|
|
113
|
+
graph_legacy = CASE WHEN ? THEN COALESCE(graph_legacy, graph) ELSE graph_legacy END,
|
|
114
|
+
graph_legacy_saved_at = CASE WHEN ? THEN COALESCE(graph_legacy_saved_at, datetime('now')) ELSE graph_legacy_saved_at END,
|
|
115
|
+
graph = ?, status = 'draft', updated_at = datetime('now') WHERE id = ?`)
|
|
116
|
+
.run(name, description, Number(converting), Number(converting), JSON.stringify(graph), id);
|
|
117
|
+
return getLoop(db, id);
|
|
118
|
+
}).immediate();
|
|
119
|
+
}
|
|
120
|
+
/** Read-only original export; does not restore, publish or execute a graph. */
|
|
121
|
+
function readLegacyLoopGraph(db, id) {
|
|
122
|
+
const row = db.prepare('SELECT graph_legacy, graph_legacy_saved_at FROM loops WHERE id = ?').get(id);
|
|
123
|
+
if (!row?.graph_legacy || !row.graph_legacy_saved_at)
|
|
66
124
|
return undefined;
|
|
67
|
-
|
|
68
|
-
const description = patch.description !== undefined ? patch.description : existing.description;
|
|
69
|
-
const graph = patch.graph ?? existing.graph;
|
|
70
|
-
db.prepare(`UPDATE loops SET name = ?, description = ?, graph = ?, status = 'draft', updated_at = datetime('now') WHERE id = ?`).run(name, description, JSON.stringify(graph), id);
|
|
71
|
-
return getLoop(db, id);
|
|
125
|
+
return { graph: JSON.parse(row.graph_legacy), savedAt: row.graph_legacy_saved_at };
|
|
72
126
|
}
|
|
73
127
|
/**
|
|
74
128
|
* Validate the graph and, if valid, mark the loop Published (so it becomes
|
|
75
129
|
* selectable in the rail picker). Throws {@link LoopValidationError} otherwise,
|
|
76
130
|
* leaving the loop in Draft.
|
|
77
131
|
*/
|
|
78
|
-
function publishLoop(db, id) {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
132
|
+
function publishLoop(db, id, validated) {
|
|
133
|
+
return db.transaction(() => {
|
|
134
|
+
const existing = getLoop(db, id);
|
|
135
|
+
if (validated && (!existing || JSON.stringify([existing.name, existing.description, existing.graph, existing.status]) !==
|
|
136
|
+
JSON.stringify([validated.name, validated.description, validated.graph, validated.status])))
|
|
137
|
+
throw new LoopPublicationConflict();
|
|
138
|
+
if (!existing)
|
|
139
|
+
return undefined;
|
|
140
|
+
const result = (0, loop_graph_1.validateLoopGraph)(existing.graph);
|
|
141
|
+
if (!result.valid)
|
|
142
|
+
throw new LoopValidationError(result.errors);
|
|
143
|
+
// The snapshot is what rails launch while a later edit is still Draft.
|
|
144
|
+
db.prepare(`UPDATE loops SET status = 'published', published_graph = graph, updated_at = datetime('now') WHERE id = ?`).run(id);
|
|
145
|
+
return getLoop(db, id);
|
|
146
|
+
}).immediate();
|
|
87
147
|
}
|
|
88
148
|
/** Return a Published loop to Draft without changing its content. */
|
|
89
149
|
function unpublishLoop(db, id) {
|
|
@@ -135,3 +195,63 @@ function importLoops(db, items, mintId) {
|
|
|
135
195
|
}
|
|
136
196
|
return { imported, skipped };
|
|
137
197
|
}
|
|
198
|
+
/**
|
|
199
|
+
* Ensure one published row per built-in default. Missing rows are inserted
|
|
200
|
+
* (`INSERT OR IGNORE`, so concurrent seeders converge on one row). With
|
|
201
|
+
* `refresh`, a row whose content still equals the default it was seeded with
|
|
202
|
+
* (unedited) is moved to the new default when that default changed — user edits
|
|
203
|
+
* are never overwritten. Rows for built-ins absent from `defaults` are kept.
|
|
204
|
+
*/
|
|
205
|
+
function seedBuiltinLoops(db, defaults, options) {
|
|
206
|
+
const result = { inserted: [], refreshed: [] };
|
|
207
|
+
db.transaction(() => {
|
|
208
|
+
for (const builtin of defaults) {
|
|
209
|
+
const hash = builtinContentHash({ name: builtin.name, description: builtin.description, graph: builtin.graph });
|
|
210
|
+
const graph = JSON.stringify(builtin.graph);
|
|
211
|
+
const inserted = db.prepare(`INSERT OR IGNORE INTO loops
|
|
212
|
+
(id, name, description, status, graph, builtin_id, builtin_default_hash, published_graph)
|
|
213
|
+
VALUES (?, ?, ?, 'published', ?, ?, ?, ?)`)
|
|
214
|
+
.run(builtin.id, builtin.name, builtin.description, graph, builtin.id, hash, graph);
|
|
215
|
+
if (inserted.changes > 0) {
|
|
216
|
+
result.inserted.push(builtin.id);
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
if (!options.refresh)
|
|
220
|
+
continue;
|
|
221
|
+
const row = db.prepare(`SELECT ${loopColumns} FROM loops WHERE builtin_id = ?`).get(builtin.id);
|
|
222
|
+
const current = mapRow(row);
|
|
223
|
+
if (!row || !current || current.builtinModified || row.builtin_default_hash === hash)
|
|
224
|
+
continue;
|
|
225
|
+
db.prepare(`UPDATE loops SET name = ?, description = ?, graph = ?, status = 'published', published_graph = ?,
|
|
226
|
+
builtin_default_hash = ?, updated_at = datetime('now') WHERE builtin_id = ?`)
|
|
227
|
+
.run(builtin.name, builtin.description, graph, graph, hash, builtin.id);
|
|
228
|
+
result.refreshed.push(builtin.id);
|
|
229
|
+
}
|
|
230
|
+
}).immediate();
|
|
231
|
+
return result;
|
|
232
|
+
}
|
|
233
|
+
/** Reset a built-in row to `builtin` (its current default), Published. */
|
|
234
|
+
function restoreBuiltinLoop(db, builtin) {
|
|
235
|
+
const hash = builtinContentHash({ name: builtin.name, description: builtin.description, graph: builtin.graph });
|
|
236
|
+
const graph = JSON.stringify(builtin.graph);
|
|
237
|
+
const updated = db.prepare(`UPDATE loops SET name = ?, description = ?, graph = ?, status = 'published', published_graph = ?,
|
|
238
|
+
builtin_default_hash = ?, updated_at = datetime('now') WHERE builtin_id = ?`)
|
|
239
|
+
.run(builtin.name, builtin.description, graph, graph, hash, builtin.id);
|
|
240
|
+
return updated.changes > 0 ? getLoop(db, builtin.id) : undefined;
|
|
241
|
+
}
|
|
242
|
+
/** The built-in row for a canonical factory id, when seeded. */
|
|
243
|
+
function getBuiltinLoop(db, builtinId) {
|
|
244
|
+
return mapRow(db.prepare(`SELECT ${loopColumns} FROM loops WHERE builtin_id = ?`).get(builtinId));
|
|
245
|
+
}
|
|
246
|
+
/** Last published graph snapshot (null when the loop was never published). */
|
|
247
|
+
function readPublishedLoopGraph(db, id) {
|
|
248
|
+
const row = db.prepare('SELECT published_graph FROM loops WHERE id = ?').get(id);
|
|
249
|
+
if (!row?.published_graph)
|
|
250
|
+
return undefined;
|
|
251
|
+
try {
|
|
252
|
+
return JSON.parse(row.published_graph);
|
|
253
|
+
}
|
|
254
|
+
catch {
|
|
255
|
+
return undefined;
|
|
256
|
+
}
|
|
257
|
+
}
|