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
|
@@ -3,10 +3,15 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.AgentRuntimeControls = exports.RuntimeControlError = void 0;
|
|
6
|
+
exports.AgentRuntimeControls = exports.RuntimeControlError = exports.STEERING_TEXT_LIMIT = void 0;
|
|
7
|
+
exports.readRuntimeSteering = readRuntimeSteering;
|
|
7
8
|
exports.pinsRailCard = pinsRailCard;
|
|
9
|
+
exports.stepIdsFor = stepIdsFor;
|
|
8
10
|
exports.validateRuntimeResumeInput = validateRuntimeResumeInput;
|
|
9
11
|
exports.readAgentRuntimeStatus = readAgentRuntimeStatus;
|
|
12
|
+
const agent_runtime_retention_1 = require("./agent-runtime-retention");
|
|
13
|
+
const agent_runtime_retention_host_1 = require("./agent-runtime-retention-host");
|
|
14
|
+
const agent_runtime_retention_records_1 = require("./agent-runtime-retention-records");
|
|
10
15
|
const agent_runtime_events_1 = require("./agent-runtime-events");
|
|
11
16
|
const agent_runtime_history_1 = require("./agent-runtime-history");
|
|
12
17
|
const agent_runtime_settlement_1 = require("./agent-runtime-settlement");
|
|
@@ -27,8 +32,32 @@ const db_1 = require("../../../db");
|
|
|
27
32
|
const loop_run_manager_1 = require("../../loops/runtime/loop-run-manager");
|
|
28
33
|
const agent_runtime_recovery_1 = require("./agent-runtime-recovery");
|
|
29
34
|
const SAFE_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
30
|
-
const
|
|
35
|
+
const LEGACY_STEP_IDS = ['architect', 'developer', 'fixer', 'verify', 'reviewer', 'archive'];
|
|
36
|
+
const NODE_PATH = /^[A-Za-z0-9][A-Za-z0-9_-]{0,119}(\/[A-Za-z0-9][A-Za-z0-9_-]{0,119}){0,3}$/;
|
|
37
|
+
function isNodePath(value) {
|
|
38
|
+
return NODE_PATH.test(value) && value.split('/').every(id => !['START', 'END', '__start__', '__end__', 'next'].includes(id));
|
|
39
|
+
}
|
|
31
40
|
const ANSWER_LIMIT = 20_000;
|
|
41
|
+
/** Core contract (engine/steering/inbox.ts): 1–20,000 UTF-16 code units per message. Its 80,000-byte cap cannot be reached below that length. */
|
|
42
|
+
exports.STEERING_TEXT_LIMIT = 20_000;
|
|
43
|
+
const STEERING_PREVIEW_LENGTH = 240;
|
|
44
|
+
const STEERING_RECEIPT_LIMIT = 512;
|
|
45
|
+
const STEERING_REQUEST_ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
|
|
46
|
+
/** Core owns receipt durability; Desktop never infers consumption from process state. */
|
|
47
|
+
function readRuntimeSteering(value) {
|
|
48
|
+
if (value === undefined)
|
|
49
|
+
return undefined;
|
|
50
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
51
|
+
throw new Error('Core returned invalid steering receipts');
|
|
52
|
+
const state = value;
|
|
53
|
+
const timestamp = (value) => typeof value === 'string' && Number.isFinite(Date.parse(value));
|
|
54
|
+
if (state.consumptionReported !== true || !Number.isSafeInteger(state.pending) || state.pending < 0 || !Number.isSafeInteger(state.consumed) || state.consumed < 0 || typeof state.truncated !== 'boolean' ||
|
|
55
|
+
!Array.isArray(state.receipts) || state.receipts.length > STEERING_RECEIPT_LIMIT || state.receipts.some(item => !item || typeof item.id !== 'string' || !item.id || item.id.length > 256 || !timestamp(item.acceptedAt) || typeof item.preview !== 'string' || item.preview.length > STEERING_PREVIEW_LENGTH || !Number.isSafeInteger(item.length) || item.length < item.preview.length || item.length > exports.STEERING_TEXT_LIMIT || !['pending', 'consumed'].includes(item.status) ||
|
|
56
|
+
(item.status === 'consumed' && (typeof item.consumedAttemptId !== 'string' || !item.consumedAttemptId || !timestamp(item.consumedAt))) ||
|
|
57
|
+
(item.status === 'pending' && (item.consumedAt !== undefined || item.consumedAttemptId !== undefined))))
|
|
58
|
+
throw new Error('Core returned invalid steering receipts');
|
|
59
|
+
return { receipts: state.receipts.map(({ id, acceptedAt, preview, length, status, consumedAttemptId, consumedAt }) => ({ id, acceptedAt, preview, length, status, ...(consumedAttemptId ? { consumedAttemptId, consumedAt } : {}) })), pending: state.pending, consumed: state.consumed, truncated: state.truncated, consumptionReported: true };
|
|
60
|
+
}
|
|
32
61
|
/**
|
|
33
62
|
* Whether a continuation still deserves the rail card. Anything that needs a
|
|
34
63
|
* human (blocked, failed, interrupted, a pending question or approval, a
|
|
@@ -38,7 +67,7 @@ const ANSWER_LIMIT = 20_000;
|
|
|
38
67
|
* kept a "Completed" card with only Dismiss on it after every green run.
|
|
39
68
|
*/
|
|
40
69
|
function pinsRailCard(summary) {
|
|
41
|
-
if (summary.dismissed)
|
|
70
|
+
if (summary.dismissed || summary.status === 'expired')
|
|
42
71
|
return false;
|
|
43
72
|
if (summary.active || summary.canResume || summary.canSettle || summary.canCancel || summary.pendingQuestion || summary.pendingApproval || summary.recoverableSteps.length)
|
|
44
73
|
return true;
|
|
@@ -54,18 +83,41 @@ class RuntimeControlError extends Error {
|
|
|
54
83
|
}
|
|
55
84
|
}
|
|
56
85
|
exports.RuntimeControlError = RuntimeControlError;
|
|
57
|
-
function
|
|
86
|
+
function stepIdsFor(state) {
|
|
87
|
+
return state.engineVersion === 2 ? Object.keys(state.steps) : [...LEGACY_STEP_IDS];
|
|
88
|
+
}
|
|
89
|
+
function metricsCatalogFor(state) {
|
|
90
|
+
return state.engineVersion === 2 ? { stepIds: stepIdsFor(state), roleIds: state.roleIds } : undefined;
|
|
91
|
+
}
|
|
92
|
+
/** Open roles belong to the original run, even after project settings change. */
|
|
93
|
+
function frozenRoleIds(contextPath) {
|
|
94
|
+
try {
|
|
95
|
+
const config = JSON.parse(node_fs_1.default.readFileSync(node_path_1.default.join(node_path_1.default.dirname(contextPath), 'desktop-runtime-config.json'), 'utf8'));
|
|
96
|
+
const catalogs = [config.agents, config.roles].filter(value => value !== undefined);
|
|
97
|
+
if (!catalogs.length || catalogs.some(value => !value || typeof value !== 'object' || Array.isArray(value)))
|
|
98
|
+
return undefined;
|
|
99
|
+
const ids = [...new Set(catalogs.flatMap(value => Object.keys(value)))];
|
|
100
|
+
return ids.every(id => /^[a-z][a-z0-9-]{0,63}$/.test(id)) ? ids : undefined;
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function validateRuntimeResumeInput(input, state) {
|
|
58
107
|
if (!input || typeof input !== 'object' || Array.isArray(input))
|
|
59
108
|
throw new RuntimeControlError(400, 'invalid_resume_request', 'Resume request must be an object');
|
|
60
109
|
const body = input;
|
|
110
|
+
const stepIds = state ? new Set(stepIdsFor(state)) : undefined;
|
|
61
111
|
for (const [key, value] of Object.entries(body)) {
|
|
62
112
|
if (key === 'answer') {
|
|
63
113
|
if (typeof value !== 'string' || !value.trim() || value.length > ANSWER_LIMIT)
|
|
64
114
|
throw new RuntimeControlError(400, 'invalid_resume_request', `An answer must be a nonempty string of at most ${ANSWER_LIMIT} characters`);
|
|
65
115
|
continue;
|
|
66
116
|
}
|
|
67
|
-
if (!['approve', 'recover', 'invalidate'].includes(key) || !Array.isArray(value) || value.length >
|
|
68
|
-
throw new RuntimeControlError(400, 'invalid_resume_request', 'Only approve, recover and invalidate arrays of
|
|
117
|
+
if (!['approve', 'recover', 'invalidate'].includes(key) || !Array.isArray(value) || value.length > 10_000 || !value.every((id) => typeof id === 'string' && isNodePath(id)) || new Set(value).size !== value.length)
|
|
118
|
+
throw new RuntimeControlError(400, 'invalid_resume_request', 'Only approve, recover and invalidate arrays of safe node paths are allowed');
|
|
119
|
+
if (stepIds && value.some(id => !stepIds.has(id)))
|
|
120
|
+
throw new RuntimeControlError(400, 'invalid_resume_request', 'Resume node paths must belong to the saved workflow');
|
|
69
121
|
}
|
|
70
122
|
return body;
|
|
71
123
|
}
|
|
@@ -90,12 +142,46 @@ async function readAgentRuntimeStatus(contextPath, cwd, env) {
|
|
|
90
142
|
const result = JSON.parse(stdout);
|
|
91
143
|
if (result.type !== 'runtime-status' || result.state === undefined)
|
|
92
144
|
throw new Error('Core returned an invalid runtime status');
|
|
145
|
+
if (result.engineVersion !== undefined && (!Number.isSafeInteger(result.engineVersion) || result.engineVersion < 1))
|
|
146
|
+
throw new Error('Core returned an invalid runtime engine version');
|
|
147
|
+
if (!result.state)
|
|
148
|
+
return null;
|
|
149
|
+
const state = { ...result.state, ...(result.engineVersion === undefined ? {} : { engineVersion: result.engineVersion }) };
|
|
150
|
+
if (state.engineVersion === 2) {
|
|
151
|
+
if (!state.steps || typeof state.steps !== 'object' || Array.isArray(state.steps) || !Object.entries(state.steps).every(([id, step]) => isNodePath(id) && step && typeof step.status === 'string'))
|
|
152
|
+
throw new Error('Core returned an invalid runtime step catalog');
|
|
153
|
+
if (state.nextNodePath !== null && (typeof state.nextNodePath !== 'string' || !Object.hasOwn(state.steps, state.nextNodePath)))
|
|
154
|
+
throw new Error('Core returned an invalid next node path');
|
|
155
|
+
if (state.recoverableSteps !== undefined && (!Array.isArray(state.recoverableSteps) || state.recoverableSteps.some(step => !step || typeof step.attemptId !== 'string' || !step.attemptId || typeof step.nodePath !== 'string' || typeof step.scopeId !== 'string')))
|
|
156
|
+
throw new Error('Core returned invalid recovery attempt identities');
|
|
157
|
+
if (state.lease !== undefined && state.lease !== null && (typeof state.lease !== 'object' || typeof state.lease.active !== 'boolean'))
|
|
158
|
+
throw new Error('Core returned an invalid execution lease');
|
|
159
|
+
if (state.pendingInterrupts !== undefined && (!Array.isArray(state.pendingInterrupts) || state.pendingInterrupts.some(item => !item || typeof item.id !== 'string' || !item.id || typeof item.nodePath !== 'string' || !['question', 'approval', 'gate'].includes(item.kind))))
|
|
160
|
+
throw new Error('Core returned invalid pending interrupts');
|
|
161
|
+
// Legacy display fields use node paths. Controls must use an exact pending
|
|
162
|
+
// interrupt, including when parallel branches pause at the same node.
|
|
163
|
+
if (state.pendingApproval) {
|
|
164
|
+
const approval = state.pendingInterrupts?.find(item => item.nodePath === state.pendingApproval.stepId && item.kind !== 'question');
|
|
165
|
+
state.pendingApproval = approval ? { ...state.pendingApproval, stepId: approval.id } : undefined;
|
|
166
|
+
}
|
|
167
|
+
if (state.pendingQuestion) {
|
|
168
|
+
const question = state.pendingInterrupts?.find(item => item.nodePath === state.pendingQuestion.stepId && item.kind === 'question');
|
|
169
|
+
state.pendingQuestion = question ? { ...state.pendingQuestion, stepId: question.id } : undefined;
|
|
170
|
+
}
|
|
171
|
+
state.nextStep = state.nextNodePath;
|
|
172
|
+
state.roleIds = frozenRoleIds(contextPath);
|
|
173
|
+
state.steering = readRuntimeSteering(state.steering);
|
|
174
|
+
if (result.completion !== undefined && result.completion !== null && (typeof result.completion !== 'object' || typeof result.completion.ok !== 'boolean' || typeof result.completion.verified !== 'boolean' || !Array.isArray(result.completion.reasons) || result.completion.reasons.some(reason => typeof reason !== 'string')))
|
|
175
|
+
throw new Error('Core returned invalid completion evidence');
|
|
176
|
+
state.completion = result.completion;
|
|
177
|
+
}
|
|
93
178
|
let selection;
|
|
94
179
|
try {
|
|
95
180
|
selection = JSON.parse(node_fs_1.default.readFileSync(node_path_1.default.join(node_path_1.default.dirname(contextPath), 'desktop-runtime-selection.json'), 'utf8'));
|
|
96
181
|
}
|
|
97
182
|
catch { /* Original hosts may not record selection origins. */ }
|
|
98
|
-
|
|
183
|
+
const catalog = metricsCatalogFor(state);
|
|
184
|
+
return { ...state, inspection: result.pipeline, metrics: (0, agent_runtime_metrics_1.readRuntimeEfficiency)(result.metrics ?? state.metrics, catalog), efficiencySummary: (0, agent_runtime_metrics_1.applyRuntimeSelectionOrigins)((0, agent_runtime_metrics_1.readRuntimeEfficiencySummary)(result.efficiencySummary, catalog), selection, state.runId) };
|
|
99
185
|
}
|
|
100
186
|
/** One controller per ProjectContext; Core's durable lease remains the final
|
|
101
187
|
* cross-process guard. Resume never constructs a new context or worktree. */
|
|
@@ -108,13 +194,91 @@ class AgentRuntimeControls {
|
|
|
108
194
|
errors = new Map();
|
|
109
195
|
statusCache = new Map();
|
|
110
196
|
disposed = false;
|
|
197
|
+
collectingRetention = false;
|
|
111
198
|
constructor(ctx, dependencies = { status: readAgentRuntimeStatus, execute: agent_runtime_bridge_1.runAgentRuntimeInvocation, kill: win_spawn_1.treeKillSafe, settle: agent_runtime_settlement_1.settleRuntimeContinuation }) {
|
|
112
199
|
this.ctx = ctx;
|
|
113
200
|
this.dependencies = dependencies;
|
|
201
|
+
// Startup recovery never blocks runtime controls; unrecovered entries stay in
|
|
202
|
+
// quarantine, where they also block package collection until reviewed.
|
|
203
|
+
try {
|
|
204
|
+
const recovered = (0, agent_runtime_retention_host_1.recoverHostRuntimeRetention)({ db: ctx.db, pipelineRoot: node_path_1.default.join((0, workspace_resolution_1.resolveProjectExecution)(ctx.project).specrailsDir, 'pipeline'), active: runId => this.active.has(runId) });
|
|
205
|
+
for (const error of recovered.errors)
|
|
206
|
+
console.warn('[agent-runtime] retention recovery kept quarantine:', error);
|
|
207
|
+
}
|
|
208
|
+
catch (error) {
|
|
209
|
+
console.warn('[agent-runtime] retention recovery unavailable:', error instanceof Error ? error.message : error);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
retentionPolicy() { return (0, agent_runtime_retention_records_1.readRuntimeRetentionPolicy)(this.ctx.db); }
|
|
213
|
+
configureRetention(input) {
|
|
214
|
+
try {
|
|
215
|
+
return (0, agent_runtime_retention_records_1.saveRuntimeRetentionPolicy)(this.ctx.db, input);
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
throw new RuntimeControlError(400, 'invalid_retention_policy', 'Retention days must be null or an integer from 1 to 3650');
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
async collectRetention(input) {
|
|
222
|
+
const body = input;
|
|
223
|
+
if (!body || typeof body !== 'object' || Array.isArray(body) || Object.keys(body).some(key => key !== 'dryRun') || body.dryRun !== undefined && typeof body.dryRun !== 'boolean')
|
|
224
|
+
throw new RuntimeControlError(400, 'invalid_retention_request', 'Retention accepts only a boolean dryRun');
|
|
225
|
+
if (this.disposed || this.collectingRetention)
|
|
226
|
+
throw new RuntimeControlError(409, 'runtime_retention_busy', 'Runtime retention is already active or shutting down');
|
|
227
|
+
this.collectingRetention = true;
|
|
228
|
+
try {
|
|
229
|
+
const policy = this.retentionPolicy(), now = Date.now();
|
|
230
|
+
const host = { db: this.ctx.db, projectId: this.ctx.project.id,
|
|
231
|
+
pipelineRoot: node_path_1.default.join((0, workspace_resolution_1.resolveProjectExecution)(this.ctx.project).specrailsDir, 'pipeline'), active: (runId) => this.active.has(runId),
|
|
232
|
+
scope: (runId) => {
|
|
233
|
+
const context = this.context(runId, true);
|
|
234
|
+
return { repositoryMounts: context.frozen.repositories.map(repository => repository.path),
|
|
235
|
+
inspect: () => this.dependencies.status(context.file, node_fs_1.default.existsSync(context.cwd) ? context.cwd : context.frozen.backlogRoot, context.env) };
|
|
236
|
+
},
|
|
237
|
+
};
|
|
238
|
+
const recovery = body.dryRun === false ? (0, agent_runtime_retention_host_1.recoverHostRuntimeRetention)(host).errors : [];
|
|
239
|
+
const report = await (0, agent_runtime_retention_1.collectRuntimeRetention)((0, agent_runtime_retention_host_1.runtimeRetentionHostPorts)(host, policy, now), policy, { dryRun: body.dryRun !== false, now });
|
|
240
|
+
return recovery.length ? { ...report, errors: [...recovery, ...report.errors] } : report;
|
|
241
|
+
}
|
|
242
|
+
finally {
|
|
243
|
+
this.collectingRetention = false;
|
|
244
|
+
this.statusCache.clear();
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
async signal(runId, input) {
|
|
248
|
+
if (this.disposed)
|
|
249
|
+
throw new RuntimeControlError(503, 'runtime_shutting_down', 'Project runtime is shutting down');
|
|
250
|
+
const body = input;
|
|
251
|
+
if (!body || typeof body !== 'object' || Array.isArray(body) || Object.keys(body).some(key => !['text', 'requestId'].includes(key)) || typeof body.text !== 'string' || !body.text.trim() || body.text.length > exports.STEERING_TEXT_LIMIT || typeof body.requestId !== 'string' || !STEERING_REQUEST_ID.test(body.requestId))
|
|
252
|
+
throw new RuntimeControlError(400, 'invalid_steering_request', 'Steering requires text of 1–20,000 characters and a stable requestId');
|
|
253
|
+
const { file, cwd, env } = this.context(runId);
|
|
254
|
+
this.assertSteeringOwner(runId);
|
|
255
|
+
const state = await this.dependencies.status(file, cwd, env);
|
|
256
|
+
this.assertSteeringOwner(runId);
|
|
257
|
+
if (!state || state.runId !== runId || state.engineVersion !== 2)
|
|
258
|
+
throw new RuntimeControlError(409, 'steering_unsupported', 'This retained run does not support engine v2 steering');
|
|
259
|
+
try {
|
|
260
|
+
const accepted = await (this.dependencies.control ?? agent_runtime_bridge_1.runAgentRuntimeControl)({ kind: 'signal', contextPath: file, cwd, env, runId, text: body.text, requestId: body.requestId });
|
|
261
|
+
this.statusCache.delete(runId);
|
|
262
|
+
return { id: accepted.id, acceptedAt: accepted.acceptedAt };
|
|
263
|
+
}
|
|
264
|
+
catch (error) {
|
|
265
|
+
const message = error instanceof Error ? error.message : 'Could not send operator steering';
|
|
266
|
+
const code = /^(control_conflict|run_terminal|inbox_full|invalid_arguments):/.exec(message)?.[1];
|
|
267
|
+
throw new RuntimeControlError(code === 'invalid_arguments' ? 400 : code ? 409 : 503, code ?? 'runtime_steering_failed', message);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
assertSteeringOwner(runId) {
|
|
271
|
+
if ((0, loop_runs_store_1.readDefinitionForkTarget)(this.ctx.db, runId)) {
|
|
272
|
+
throw new RuntimeControlError(409, 'runtime_fork_pending', 'This run has a pending or adopted fork; send steering to the child after adoption');
|
|
273
|
+
}
|
|
114
274
|
}
|
|
115
275
|
context(runId, historical = false) {
|
|
116
276
|
if (!SAFE_ID.test(runId))
|
|
117
277
|
throw new RuntimeControlError(400, 'invalid_run_id', 'Invalid runtime run ID');
|
|
278
|
+
if ((0, agent_runtime_retention_records_1.readRuntimeExpiration)(this.ctx.db, runId))
|
|
279
|
+
throw new RuntimeControlError(410, 'runtime_history_expired', 'Runtime history has expired under the project retention policy');
|
|
280
|
+
if (!historical && (0, loop_runs_store_1.readDefinitionExecutionClaim)(this.ctx.db, runId)?.owner.startsWith('retention'))
|
|
281
|
+
throw new RuntimeControlError(409, 'runtime_retention_busy', 'Runtime history is reserved for retention');
|
|
118
282
|
const execution = (0, workspace_resolution_1.resolveProjectExecution)(this.ctx.project);
|
|
119
283
|
const backlogRoot = node_fs_1.default.realpathSync(node_path_1.default.dirname(execution.specrailsDir));
|
|
120
284
|
const directory = node_path_1.default.join(execution.specrailsDir, 'pipeline', runId);
|
|
@@ -259,23 +423,30 @@ class AgentRuntimeControls {
|
|
|
259
423
|
}
|
|
260
424
|
async list() {
|
|
261
425
|
const directory = node_path_1.default.join((0, workspace_resolution_1.resolveProjectExecution)(this.ctx.project).specrailsDir, 'pipeline');
|
|
262
|
-
|
|
263
|
-
return [];
|
|
264
|
-
const entries = node_fs_1.default.readdirSync(directory, { withFileTypes: true }).filter((entry) => entry.isDirectory() && SAFE_ID.test(entry.name) && node_fs_1.default.existsSync(node_path_1.default.join(directory, entry.name, 'agent-runtime-request.json')))
|
|
426
|
+
const entries = (node_fs_1.default.existsSync(directory) ? node_fs_1.default.readdirSync(directory, { withFileTypes: true }) : []).filter((entry) => entry.isDirectory() && SAFE_ID.test(entry.name) && node_fs_1.default.existsSync(node_path_1.default.join(directory, entry.name, 'agent-runtime-request.json')))
|
|
265
427
|
.sort((a, b) => node_fs_1.default.statSync(node_path_1.default.join(directory, b.name)).mtimeMs - node_fs_1.default.statSync(node_path_1.default.join(directory, a.name)).mtimeMs).slice(0, 20);
|
|
266
428
|
const runs = [];
|
|
267
429
|
// Bounded sequential reads avoid spawning one Node process per historical run at once.
|
|
268
430
|
for (const entry of entries)
|
|
269
431
|
runs.push(await this.summary(entry.name));
|
|
270
|
-
|
|
432
|
+
const expired = this.ctx.db.prepare('SELECT run_id FROM runtime_retention_records ORDER BY expired_at DESC LIMIT 20').all();
|
|
433
|
+
for (const row of expired)
|
|
434
|
+
if (!runs.some(run => run.runId === row.run_id))
|
|
435
|
+
runs.push(await this.summary(row.run_id));
|
|
436
|
+
return runs.sort((a, b) => Number(b.active) - Number(a.active) || (b.updatedAt ?? '').localeCompare(a.updatedAt ?? '')).slice(0, 20);
|
|
271
437
|
}
|
|
272
438
|
async summary(runId) {
|
|
439
|
+
const expired = (0, agent_runtime_retention_records_1.readRuntimeExpiration)(this.ctx.db, runId);
|
|
440
|
+
if (expired)
|
|
441
|
+
return { runId, status: 'expired', expiredAt: expired.expiredAt, updatedAt: expired.expiredAt, nextStep: null, historical: true, active: false, canResume: false, canCancel: false, canSettle: false, recoverableSteps: [] };
|
|
273
442
|
try {
|
|
274
443
|
const { file, cwd, env } = this.context(runId);
|
|
275
444
|
// Core owns checkpoint parsing. Its atomic file revision is only a cache
|
|
276
445
|
// key, keeping idle settings panes from spawning CLI processes repeatedly.
|
|
277
446
|
const checkpoint = node_path_1.default.join(node_path_1.default.dirname(file), 'agent-workflow', runId, 'checkpoint.json');
|
|
278
|
-
|
|
447
|
+
// A v2 run can also contain a legacy implementation journal; that file
|
|
448
|
+
// cannot invalidate the SQLite/WAL status, so leave v2 inspection uncached.
|
|
449
|
+
const stat = !node_fs_1.default.existsSync(node_path_1.default.join(node_path_1.default.dirname(file), 'agent-workflow', 'run.sqlite')) && node_fs_1.default.existsSync(checkpoint) ? node_fs_1.default.statSync(checkpoint) : null;
|
|
279
450
|
const fingerprint = stat ? (0, node_crypto_1.createHash)('sha256').update(node_fs_1.default.readFileSync(checkpoint)).digest('hex') : null;
|
|
280
451
|
const cached = this.statusCache.get(runId);
|
|
281
452
|
const state = fingerprint && cached?.fingerprint === fingerprint ? cached.state : await this.dependencies.status(file, cwd, env);
|
|
@@ -284,20 +455,26 @@ class AgentRuntimeControls {
|
|
|
284
455
|
if (fingerprint)
|
|
285
456
|
this.statusCache.set(runId, { fingerprint, state });
|
|
286
457
|
const parent = (0, loop_runs_store_1.getLoopRun)(this.ctx.db, runId);
|
|
287
|
-
const
|
|
458
|
+
const definition = state.engineVersion === 2 && parent?.engine_version === 2;
|
|
459
|
+
const forkSuccessor = definition ? (0, loop_runs_store_1.readDefinitionSuccessor)(this.ctx.db, runId) : undefined;
|
|
460
|
+
const forkReserved = definition && !!(0, loop_runs_store_1.readDefinitionForkTarget)(this.ctx.db, runId);
|
|
461
|
+
const active = definition ? state.lease?.active === true : this.active.has(runId) || parent?.status === 'running' || parent?.status === 'paused';
|
|
288
462
|
let projection = null;
|
|
289
463
|
try {
|
|
290
464
|
projection = (0, agent_runtime_history_1.readRuntimeHistory)(file);
|
|
291
465
|
}
|
|
292
466
|
catch { /* Invalid advisory history cannot replace live state. */ }
|
|
293
467
|
const superseding = !active && projection && ['failed', 'cancelled', 'running'].includes(projection.status) && Date.parse(projection.updatedAt) > Date.parse(state.updatedAt ?? '1970-01-01') ? projection : null;
|
|
294
|
-
const recoverableSteps = Object.entries(state.steps).filter(([, step]) => ['running', 'interrupted'].includes(step.status)).map(([id]) => id);
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
468
|
+
const recoverableSteps = state.engineVersion === 2 ? (state.recoverableSteps ?? []).map(step => step.attemptId) : Object.entries(state.steps).filter(([, step]) => ['running', 'interrupted'].includes(step.status)).map(([id]) => id);
|
|
469
|
+
const catalog = metricsCatalogFor(state);
|
|
470
|
+
return { runId, engineVersion: state.engineVersion, status: superseding ? (superseding.status === 'running' ? 'interrupted' : superseding.status) : state.status === 'running' && !active ? 'interrupted' : state.status, nextStep: state.nextStep,
|
|
471
|
+
updatedAt: superseding?.updatedAt ?? state.updatedAt, error: this.errors.get(runId) ?? superseding?.error ?? state.error, pendingApproval: forkReserved ? undefined : state.pendingApproval,
|
|
472
|
+
traceId: state.traceId, pendingQuestion: forkReserved ? undefined : openQuestion(state), steering: state.steering, completion: state.completion, ...(forkSuccessor ? { forkSuccessor } : {}),
|
|
473
|
+
metrics: (0, agent_runtime_metrics_1.readRuntimeEfficiency)(superseding ? superseding.metrics : state.metrics, catalog), efficiencySummary: (0, agent_runtime_metrics_1.readRuntimeEfficiencySummary)(superseding ? superseding.efficiencySummary : state.efficiencySummary, catalog),
|
|
474
|
+
canSettle: forkReserved ? false : definition
|
|
475
|
+
? !active && parent.status === 'completed' && state.status === 'succeeded' && !!this.ctx.db.prepare(`SELECT 1 FROM definition_delivery_settlements s JOIN rail_pr_deliveries d ON d.id=s.delivery_id WHERE s.project_id=? AND s.run_id=? AND d.decision IN ('building','pr_failed','implementation_failed') LIMIT 1`).get(this.ctx.project.id, runId)
|
|
476
|
+
: !superseding && !active && state.status === 'succeeded' && this.ctx.db.prepare('SELECT status FROM jobs WHERE id = ?').get(runId)?.status !== 'completed',
|
|
477
|
+
recoverableSteps: forkReserved ? [] : recoverableSteps, ...(state.engineVersion === 2 ? { recoveryAttempts: forkReserved ? [] : state.recoverableSteps ?? [] } : {}), active, canCancel: !forkReserved && (definition ? !state.completion && !['succeeded', 'cancelled'].includes(state.status) : this.active.has(runId)), canResume: !forkReserved && (state.engineVersion === 2 ? definition && !active && parent.status !== 'completed' && state.status !== 'cancelled' : !active && parent?.status === 'completed' && state.status !== 'succeeded'),
|
|
301
478
|
canDismiss: !active, dismissed: this.isDismissed(runId) };
|
|
302
479
|
}
|
|
303
480
|
catch (error) {
|
|
@@ -315,6 +492,8 @@ class AgentRuntimeControls {
|
|
|
315
492
|
if (this.disposed)
|
|
316
493
|
throw new RuntimeControlError(503, 'runtime_shutting_down', 'Project runtime is shutting down');
|
|
317
494
|
const parent = (0, loop_runs_store_1.getLoopRun)(this.ctx.db, runId);
|
|
495
|
+
if (parent?.engine_version === 2)
|
|
496
|
+
throw new RuntimeControlError(409, 'definition_lifecycle_required', 'Resume this workflow through its definition lifecycle');
|
|
318
497
|
if (!parent || parent.status !== 'completed' || this.active.has(runId))
|
|
319
498
|
throw new RuntimeControlError(409, 'runtime_run_active', 'Wait for the original Desktop execution to settle before resuming');
|
|
320
499
|
if (parent.rail_index != null && [...(this.ctx.railLoopRuns?.values() ?? []), ...(this.ctx.railJobs?.values() ?? [])].some(meta => meta.railIndex === parent.rail_index))
|
|
@@ -326,10 +505,13 @@ class AgentRuntimeControls {
|
|
|
326
505
|
try {
|
|
327
506
|
// Accounting from a prior continuation must settle even when Core now
|
|
328
507
|
// reports success and correctly refuses any further execution.
|
|
329
|
-
(0, loop_run_manager_1.recoverOrphanLoopStepAccounting)(this.ctx.db, new Date().toISOString(), runId);
|
|
330
508
|
const state = await this.dependencies.status(context.file, context.cwd, context.env);
|
|
509
|
+
if (state?.engineVersion === 2)
|
|
510
|
+
throw new RuntimeControlError(409, 'definition_lifecycle_required', 'Resume this workflow through its definition lifecycle');
|
|
511
|
+
(0, loop_run_manager_1.recoverOrphanLoopStepAccounting)(this.ctx.db, new Date().toISOString(), runId);
|
|
331
512
|
if (!state || state.runId !== runId || state.status === 'succeeded')
|
|
332
513
|
throw new RuntimeControlError(409, 'runtime_not_resumable', 'This execution has no resumable workflow');
|
|
514
|
+
validateRuntimeResumeInput(input, state);
|
|
333
515
|
if (parent.rail_index != null && [...(this.ctx.railLoopRuns?.values() ?? []), ...(this.ctx.railJobs?.values() ?? [])].some(meta => meta.railIndex === parent.rail_index))
|
|
334
516
|
throw new RuntimeControlError(409, 'runtime_rail_active', 'The implementation card became active while checking the saved execution');
|
|
335
517
|
// A pending question resumes the architect only with the operator's answer.
|
|
@@ -1,8 +1,48 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.workflowRoleDefaults = workflowRoleDefaults;
|
|
4
|
+
exports.bindWorkflowRoleSelections = bindWorkflowRoleSelections;
|
|
5
|
+
exports.bindWorkflowRoleDefaults = bindWorkflowRoleDefaults;
|
|
3
6
|
exports.resolveEffectiveRuntimeConfig = resolveEffectiveRuntimeConfig;
|
|
4
7
|
exports.fillDefaultRoleModels = fillDefaultRoleModels;
|
|
5
8
|
const providers_1 = require("../../../providers");
|
|
9
|
+
/** The loop decision role uses the selected review engine, but has no OpenSpec
|
|
10
|
+
* workflow obligation. Native implementation's reviewer keeps its own policy. */
|
|
11
|
+
function workflowRoleDefaults(config) {
|
|
12
|
+
return { 'loop-decider': { ...config.agents.reviewer, access: 'read', artifacts: 'none' } };
|
|
13
|
+
}
|
|
14
|
+
/** Explicit launch-owned roles, used when a converted workflow has a different
|
|
15
|
+
* decision engine from the project's reviewer. Validate the complete selection
|
|
16
|
+
* before applying it; project settings and caller descriptors remain untouched. */
|
|
17
|
+
function bindWorkflowRoleSelections(config, definition, selections) {
|
|
18
|
+
if (!selections || !Object.keys(selections).length)
|
|
19
|
+
return {};
|
|
20
|
+
const declared = definition?.roles;
|
|
21
|
+
const names = Object.keys(selections);
|
|
22
|
+
if (!Array.isArray(declared) || names.some(id => !declared.includes(id) ||
|
|
23
|
+
!/^[a-z][a-z0-9-]{0,63}$/.test(id) ||
|
|
24
|
+
['architect', 'developer', 'reviewer', 'fixer', 'constructor', 'prototype'].includes(id))) {
|
|
25
|
+
throw new Error('Workflow role selections must name declared custom roles');
|
|
26
|
+
}
|
|
27
|
+
config.roles = { ...config.roles, ...structuredClone(selections) };
|
|
28
|
+
fillDefaultRoleModels(config);
|
|
29
|
+
return Object.fromEntries(names.map(id => [id, 'explicit-workflow-selection']));
|
|
30
|
+
}
|
|
31
|
+
/** Bind only roles referenced by the frozen definition; preserve explicit
|
|
32
|
+
* project assignments and never persist generated defaults into project files. */
|
|
33
|
+
function bindWorkflowRoleDefaults(config, definition) {
|
|
34
|
+
const roles = definition?.roles;
|
|
35
|
+
if (!Array.isArray(roles) || !roles.includes('loop-decider'))
|
|
36
|
+
return {};
|
|
37
|
+
const existing = config.roles?.['loop-decider'];
|
|
38
|
+
if (existing) {
|
|
39
|
+
if (existing.access !== 'read' || existing.artifacts !== 'none' || existing.openspecSkill)
|
|
40
|
+
throw new Error('loop-decider requires read access, no artifact writes and no OpenSpec skill');
|
|
41
|
+
return { 'loop-decider': 'project-role' };
|
|
42
|
+
}
|
|
43
|
+
config.roles = { ...config.roles, ...workflowRoleDefaults(config) };
|
|
44
|
+
return { 'loop-decider': 'inherited-reviewer-engine' };
|
|
45
|
+
}
|
|
6
46
|
/** A pure admission resolver; only deliberate launch intent overrides a project role. */
|
|
7
47
|
function resolveEffectiveRuntimeConfig(input, options) {
|
|
8
48
|
const config = structuredClone(input);
|
|
@@ -29,6 +69,11 @@ function resolveEffectiveRuntimeConfig(input, options) {
|
|
|
29
69
|
config.agents[role] = next;
|
|
30
70
|
}
|
|
31
71
|
}
|
|
72
|
+
if (override && config.roles)
|
|
73
|
+
for (const [id, current] of Object.entries(config.roles)) {
|
|
74
|
+
const changed = current.provider !== override.provider || (override.model !== undefined && override.model !== current.model);
|
|
75
|
+
config.roles[id] = { ...current, provider: override.provider, model: override.model ?? (current.provider !== override.provider ? undefined : current.model), effort: override.effort ?? (changed ? undefined : current.effort), escalation: changed ? undefined : current.escalation };
|
|
76
|
+
}
|
|
32
77
|
fillDefaultRoleModels(config);
|
|
33
78
|
config.verification = config.verification.filter(check => options.repositoryIds.includes(check.repositoryId));
|
|
34
79
|
return { config, origins: { architect: override ? 'explicit-launch-override' : options.source, developer: override ? 'explicit-launch-override' : options.source, reviewer: override ? 'explicit-launch-override' : options.source } };
|
|
@@ -42,7 +87,7 @@ function resolveEffectiveRuntimeConfig(input, options) {
|
|
|
42
87
|
* "Role architect requires a model for its API provider".
|
|
43
88
|
*/
|
|
44
89
|
function fillDefaultRoleModels(config, options = {}) {
|
|
45
|
-
for (const agent of [...Object.values(config.agents), ...(config.fixer ? [config.fixer] : [])]) {
|
|
90
|
+
for (const agent of [...Object.values(config.agents), ...Object.values(config.roles ?? {}), ...(config.fixer ? [config.fixer] : [])]) {
|
|
46
91
|
const provider = config.providers.find(entry => entry.id === agent.provider);
|
|
47
92
|
if (!agent.model && provider?.kind === 'cli' && !options.localOnly)
|
|
48
93
|
agent.model = (0, providers_1.getAdapter)(provider.cli).defaultModel();
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.supportedEngines = supportedEngines;
|
|
4
|
+
/** Engines the active Core can launch. Cores released before the `engines`
|
|
5
|
+
* field still run the legacy engine, plus definitions when they advertise engine v2. */
|
|
6
|
+
function supportedEngines(api) {
|
|
7
|
+
if (api?.engines)
|
|
8
|
+
return [...api.engines];
|
|
9
|
+
return api?.capabilities?.engineV2 === 1 ? [1, 2] : [1];
|
|
10
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.readWorkflowCatalog = readWorkflowCatalog;
|
|
3
4
|
exports.findCoreAgentRuntimeEntry = findCoreAgentRuntimeEntry;
|
|
4
5
|
exports.resetCoreAgentRuntimeApiCache = resetCoreAgentRuntimeApiCache;
|
|
5
6
|
exports.loadCoreAgentRuntime = loadCoreAgentRuntime;
|
|
@@ -15,6 +16,35 @@ const core_runtime_1 = require("../../../core-runtime");
|
|
|
15
16
|
const core_node_runtime_1 = require("../../../core-node-runtime");
|
|
16
17
|
const win_spawn_1 = require("../../../util/win-spawn");
|
|
17
18
|
const agent_runtime_package_1 = require("./agent-runtime-package");
|
|
19
|
+
function readWorkflowCatalog(value) {
|
|
20
|
+
if (!object(value) || value.type !== 'runtime-workflows' || !object(value.definitionSchema) || !Number.isSafeInteger(value.nodeKindsVersion) || Number(value.nodeKindsVersion) < 1 ||
|
|
21
|
+
!Array.isArray(value.nodeKinds) || value.nodeKinds.length > 128 || !Array.isArray(value.builtins) ||
|
|
22
|
+
!value.nodeKinds.every(piece => object(piece) && typeof piece.kind === 'string' && /^[a-z][a-z0-9-]{0,63}$/.test(piece.kind) && object(piece.paramsSchema) &&
|
|
23
|
+
Array.isArray(piece.outcomes) && piece.outcomes.every(label => typeof label === 'string' && /^[a-z][a-z0-9-]{0,31}$/.test(label)) &&
|
|
24
|
+
new Set(piece.outcomes).size === piece.outcomes.length && ['read', 'write', 'derived'].includes(String(piece.effect)) && typeof piece.requiresAI === 'boolean') ||
|
|
25
|
+
new Set(value.nodeKinds.map(piece => piece.kind)).size !== value.nodeKinds.length ||
|
|
26
|
+
!value.builtins.every(item => object(item) && typeof item.id === 'string' && typeof item.version === 'string' && typeof item.deprecated === 'boolean')) {
|
|
27
|
+
throw new Error('Core returned malformed workflow catalog');
|
|
28
|
+
}
|
|
29
|
+
return value;
|
|
30
|
+
}
|
|
31
|
+
function object(value) {
|
|
32
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
33
|
+
}
|
|
34
|
+
/** Validation is a protocol result even when Core exits 1 for an invalid definition. */
|
|
35
|
+
function readDefinitionValidation(value) {
|
|
36
|
+
const invalid = () => { throw new Error('Core returned malformed workflow definition validation'); };
|
|
37
|
+
if (!object(value) || value.type !== 'runtime-definition-validated')
|
|
38
|
+
return invalid();
|
|
39
|
+
if (value.ok === true) {
|
|
40
|
+
if (typeof value.version !== 'string' || !/^[a-f0-9]{64}$/.test(value.version) || !object(value.definition) || value.definition.version !== value.version || !object(value.graph) || !Array.isArray(value.graph.nodes) || !Array.isArray(value.graph.edges))
|
|
41
|
+
return invalid();
|
|
42
|
+
return { ok: true, version: value.version, definition: value.definition, graph: { nodes: value.graph.nodes, edges: value.graph.edges } };
|
|
43
|
+
}
|
|
44
|
+
if (value.ok !== false || !Array.isArray(value.errors) || !value.errors.length || !value.errors.every(error => object(error) && typeof error.code === 'string' && error.code.length > 0 && typeof error.message === 'string' && ['nodeId', 'path'].every(key => error[key] === undefined || typeof error[key] === 'string')))
|
|
45
|
+
return invalid();
|
|
46
|
+
return { ok: false, errors: value.errors };
|
|
47
|
+
}
|
|
18
48
|
/** An explicit or bundled installation is authoritative: never silently replace
|
|
19
49
|
* a broken production bundle with a developer's checkout. */
|
|
20
50
|
function findCoreAgentRuntimeEntry() {
|
|
@@ -73,6 +103,17 @@ async function loadCoreAgentRuntime() {
|
|
|
73
103
|
throw new Error('Core returned malformed runtime capabilities');
|
|
74
104
|
if (api.workflowVersions !== undefined && (!Array.isArray(api.workflowVersions) || !api.workflowVersions.every(value => typeof value === 'string' && /^\d+$/.test(value))))
|
|
75
105
|
throw new Error('Core returned malformed workflow versions');
|
|
106
|
+
for (const key of ['engineVersion', 'nodeKindsVersion']) {
|
|
107
|
+
const value = api[key];
|
|
108
|
+
if (value !== undefined && (!Number.isSafeInteger(value) || value < (key === 'engineVersion' ? 1 : 0)))
|
|
109
|
+
throw new Error(`Core returned malformed ${key}`);
|
|
110
|
+
}
|
|
111
|
+
if (api.engines !== undefined && (!Array.isArray(api.engines) || !api.engines.length || !api.engines.every(value => Number.isSafeInteger(value) && value >= 1) || new Set(api.engines).size !== api.engines.length))
|
|
112
|
+
throw new Error('Core returned malformed engines');
|
|
113
|
+
if (api.nodeKinds !== undefined && (!Array.isArray(api.nodeKinds) || !api.nodeKinds.every(value => typeof value === 'string' && /^[a-z][a-z0-9-]{0,63}$/.test(value)) || new Set(api.nodeKinds).size !== api.nodeKinds.length))
|
|
114
|
+
throw new Error('Core returned malformed node kinds');
|
|
115
|
+
if (api.builtins !== undefined && (!Array.isArray(api.builtins) || !api.builtins.every(value => object(value) && typeof value.id === 'string' && /^[a-z0-9][a-z0-9-]{0,63}$/.test(value.id) && typeof value.version === 'string' && value.version.length > 0 && value.version.length <= 128 && typeof value.deprecated === 'boolean') || new Set(api.builtins.map(value => value.id)).size !== api.builtins.length))
|
|
116
|
+
throw new Error('Core returned malformed builtins');
|
|
76
117
|
if (api.runtimeIdentity !== undefined) {
|
|
77
118
|
const identity = api.runtimeIdentity;
|
|
78
119
|
if (!identity || identity.apiVersion !== 1 || !['packageVersion', 'workflowVersion', 'instructionsVersion', 'packageIntegrity'].every(key => typeof identity[key] === 'string') || !/^sha256:[a-f0-9]{64}$/.test(identity.packageIntegrity))
|
|
@@ -89,6 +130,11 @@ async function loadCoreAgentRuntime() {
|
|
|
89
130
|
const result = JSON.parse(invoke(['capabilities', '--stdin'], JSON.stringify(input)));
|
|
90
131
|
return { ...validateRoleCapabilities(result, input), runtimeIdentity: api.runtimeIdentity };
|
|
91
132
|
},
|
|
133
|
+
listWorkflows() {
|
|
134
|
+
if (api.capabilities?.workflowDefinitions !== 1 || api.capabilities?.engineV2 !== 1)
|
|
135
|
+
throw new Error('Installed Core does not support workflow definitions. Update the paired Core package.');
|
|
136
|
+
return readWorkflowCatalog(JSON.parse(invoke(['workflows', 'list'])));
|
|
137
|
+
},
|
|
92
138
|
rolePromptDefaults() {
|
|
93
139
|
const result = JSON.parse(invoke(['prompts']));
|
|
94
140
|
if (result.type !== 'runtime-role-prompts' || !['architect', 'developer', 'reviewer'].every(role => typeof result.defaults?.[role] === 'string' && result.defaults[role].trim()))
|
|
@@ -102,6 +148,25 @@ async function loadCoreAgentRuntime() {
|
|
|
102
148
|
throw new Error('Core did not validate the runtime configuration');
|
|
103
149
|
return input;
|
|
104
150
|
},
|
|
151
|
+
validateWorkflowDefinition(input, options) {
|
|
152
|
+
if (api.capabilities?.workflowDefinitions !== 1)
|
|
153
|
+
throw new Error('Installed Core does not support workflow definitions. Update the paired Core package.');
|
|
154
|
+
const args = ['workflows', 'validate', '--stdin', ...(options?.configPath ? ['--config', options.configPath] : []), ...(options?.structural !== false && !options?.configPath ? ['--structural'] : [])];
|
|
155
|
+
let output;
|
|
156
|
+
try {
|
|
157
|
+
output = invoke(args, JSON.stringify(input));
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
const failure = error;
|
|
161
|
+
if (failure.status !== 1 || typeof failure.stdout !== 'string')
|
|
162
|
+
throw error;
|
|
163
|
+
const result = readDefinitionValidation(JSON.parse(failure.stdout));
|
|
164
|
+
if (result.ok)
|
|
165
|
+
throw error;
|
|
166
|
+
return result;
|
|
167
|
+
}
|
|
168
|
+
return readDefinitionValidation(JSON.parse(output));
|
|
169
|
+
},
|
|
105
170
|
};
|
|
106
171
|
}
|
|
107
172
|
function requireRuntimeCapabilities(api, input) {
|
|
@@ -118,8 +183,8 @@ function validateRoleCapabilities(value, input) {
|
|
|
118
183
|
const invalid = () => { throw new Error('Core returned malformed role capabilities'); };
|
|
119
184
|
const object = (value) => value && typeof value === 'object' && !Array.isArray(value) ? value : invalid();
|
|
120
185
|
const result = object(value), config = object(input);
|
|
121
|
-
const choices = Object.entries(object(config.agents));
|
|
122
|
-
if (result.type !== 'runtime-capabilities' || result.schemaVersion !== 1 || !Array.isArray(result.roles)
|
|
186
|
+
const choices = Object.entries({ ...object(config.agents), ...(config.roles === undefined ? {} : object(config.roles)) });
|
|
187
|
+
if (result.type !== 'runtime-capabilities' || result.schemaVersion !== 1 || !Array.isArray(result.roles))
|
|
123
188
|
return invalid();
|
|
124
189
|
const expected = choices.flatMap(([role, value]) => {
|
|
125
190
|
const selected = object(value);
|
|
@@ -152,7 +217,7 @@ function validateRoleCapabilities(value, input) {
|
|
|
152
217
|
return { type: 'runtime-capabilities', schemaVersion: 1, roles };
|
|
153
218
|
}
|
|
154
219
|
function validateRequestedRoleEfforts(runtime, config) {
|
|
155
|
-
if (!Object.values(config.agents).some(role => role.effort !== undefined || role.escalation?.effort !== undefined))
|
|
220
|
+
if (![...Object.values(config.agents), ...Object.values(config.roles ?? {})].some(role => role.effort !== undefined || role.escalation?.effort !== undefined))
|
|
156
221
|
return;
|
|
157
222
|
if (!runtime.capabilities)
|
|
158
223
|
throw new Error('Installed Core cannot validate requested role effort');
|
|
@@ -6,6 +6,7 @@ exports.applyRuntimeSelectionOrigins = applyRuntimeSelectionOrigins;
|
|
|
6
6
|
const COUNTS = ['attempts', 'measuredAttempts', 'providerCalls', 'toolCalls', 'inputTokens', 'outputTokens', 'uncachedInputTokens', 'cacheReadInputTokens', 'cacheWriteInputTokens'];
|
|
7
7
|
const KEYS = [...COUNTS, 'durationMs', 'agentDurationMs', 'costUsd'];
|
|
8
8
|
const PHASES = ['architect', 'developer', 'fixer', 'verify', 'reviewer', 'archive'];
|
|
9
|
+
const ROLES = ['architect', 'developer', 'reviewer'];
|
|
9
10
|
function object(value) { return value !== null && typeof value === 'object' && !Array.isArray(value) ? value : null; }
|
|
10
11
|
function counters(value) {
|
|
11
12
|
const input = object(value);
|
|
@@ -27,28 +28,32 @@ function counters(value) {
|
|
|
27
28
|
return output;
|
|
28
29
|
}
|
|
29
30
|
/** Older Core versions omit metrics; malformed/unknown versions do not break run controls. */
|
|
30
|
-
function readRuntimeEfficiency(value) {
|
|
31
|
+
function readRuntimeEfficiency(value, catalog) {
|
|
31
32
|
const input = object(value);
|
|
32
|
-
|
|
33
|
+
const stepIds = catalog?.stepIds ?? PHASES;
|
|
34
|
+
if (input?.schemaVersion !== 1 || !Array.isArray(input.phases) || input.phases.length > stepIds.length)
|
|
33
35
|
return undefined;
|
|
34
36
|
const total = counters(input.total);
|
|
35
37
|
if (!total)
|
|
36
38
|
return undefined;
|
|
39
|
+
const allowedSteps = new Set(stepIds), seenSteps = new Set();
|
|
37
40
|
const phases = [];
|
|
38
41
|
for (const raw of input.phases) {
|
|
39
42
|
const phase = object(raw), count = counters(phase);
|
|
40
|
-
if (!phase || !count || typeof phase.stepId !== 'string' || !
|
|
43
|
+
if (!phase || !count || typeof phase.stepId !== 'string' || !allowedSteps.has(phase.stepId) || seenSteps.has(phase.stepId))
|
|
41
44
|
return undefined;
|
|
42
45
|
for (const key of ['providers', 'models'])
|
|
43
46
|
if (!Array.isArray(phase[key]) || phase[key].length > 50 || !phase[key].every((item) => typeof item === 'string' && item.length > 0 && item.length <= 256))
|
|
44
47
|
return undefined;
|
|
45
48
|
phases.push({ ...count, stepId: phase.stepId, providers: [...phase.providers], models: [...phase.models] });
|
|
49
|
+
seenSteps.add(phase.stepId);
|
|
46
50
|
}
|
|
47
51
|
return { schemaVersion: 1, total, phases };
|
|
48
52
|
}
|
|
49
|
-
function readRuntimeEfficiencySummary(value) {
|
|
53
|
+
function readRuntimeEfficiencySummary(value, catalog) {
|
|
50
54
|
const input = object(value), invocations = object(input?.invocations), checks = object(input?.checks);
|
|
51
|
-
|
|
55
|
+
const roleIds = catalog?.roleIds ?? ROLES;
|
|
56
|
+
if (!input || input.schemaVersion !== 1 || typeof input.runId !== 'string' || input.runId.length > 128 || typeof input.workflowVersion !== 'string' || input.workflowVersion.length > (catalog ? 64 : 16) || !['pending', 'validated', 'with-exceptions', 'blocked'].includes(String(input.technicalAcceptance)) || !invocations || !checks || !Array.isArray(input.roles) || input.roles.length > roleIds.length)
|
|
52
57
|
return undefined;
|
|
53
58
|
if (typeof input.archive !== 'string' || input.archive.length > 32 || typeof input.delivery !== 'string' || input.delivery.length > 32 || typeof invocations.complete !== 'boolean' || typeof checks.available !== 'boolean')
|
|
54
59
|
return undefined;
|
|
@@ -77,7 +82,7 @@ function readRuntimeEfficiencySummary(value) {
|
|
|
77
82
|
const roles = [];
|
|
78
83
|
for (const raw of input.roles) {
|
|
79
84
|
const role = object(raw);
|
|
80
|
-
if (!role || !
|
|
85
|
+
if (!role || !roleIds.includes(String(role.role)) || roles.some(item => item.role === role.role))
|
|
81
86
|
return undefined;
|
|
82
87
|
const row = { role: String(role.role) };
|
|
83
88
|
for (const key of ['provider', 'model', 'effort', 'observedModel', 'observedEffort', 'origin', 'tier']) {
|
|
@@ -96,7 +101,7 @@ function readRuntimeEfficiencySummary(value) {
|
|
|
96
101
|
escalations = [];
|
|
97
102
|
for (const item of input.escalations) {
|
|
98
103
|
const raw = object(item);
|
|
99
|
-
if (!raw || !
|
|
104
|
+
if (!raw || !roleIds.includes(String(raw.role)))
|
|
100
105
|
return undefined;
|
|
101
106
|
const row = { role: String(raw.role) };
|
|
102
107
|
for (const key of ['attemptId', 'provider', 'model', 'effort', 'reason']) {
|
|
@@ -118,5 +123,8 @@ function applyRuntimeSelectionOrigins(summary, selection, runId) {
|
|
|
118
123
|
return summary;
|
|
119
124
|
if (!['architect', 'developer', 'reviewer'].every(role => ['project-role', 'default', ...(role === 'developer' ? ['explicit-launch-override'] : [])].includes(String(origins[role]))))
|
|
120
125
|
return summary;
|
|
121
|
-
return { ...summary, roles: summary.roles.map(role =>
|
|
126
|
+
return { ...summary, roles: summary.roles.map(role => {
|
|
127
|
+
const origin = origins[role.role];
|
|
128
|
+
return { ...role, origin: typeof origin === 'string' ? origin : role.origin };
|
|
129
|
+
}) };
|
|
122
130
|
}
|