muonroi-cli 1.8.4 → 1.8.5
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/LICENSE +17 -5
- package/dist/packages/agent-harness-core/src/driver.d.ts +27 -1
- package/dist/packages/agent-harness-core/src/driver.js +46 -0
- package/dist/packages/agent-harness-core/src/event-tee.d.ts +48 -0
- package/dist/packages/agent-harness-core/src/event-tee.js +77 -0
- package/dist/packages/agent-harness-core/src/mcp-server.d.ts +11 -0
- package/dist/packages/agent-harness-core/src/mcp-server.js +87 -15
- package/dist/packages/agent-harness-core/src/protocol.d.ts +66 -2
- package/dist/packages/agent-harness-core/src/protocol.js +15 -0
- package/dist/packages/agent-harness-core/src/visual-quality.d.ts +58 -0
- package/dist/packages/agent-harness-core/src/visual-quality.js +141 -0
- package/dist/packages/agent-harness-opentui/src/agent-mode.d.ts +6 -0
- package/dist/packages/agent-harness-opentui/src/agent-mode.js +14 -1
- package/dist/packages/agent-harness-opentui/src/input-bridge.d.ts +2 -10
- package/dist/packages/agent-harness-opentui/src/input-bridge.js +103 -16
- package/dist/packages/agent-harness-opentui/src/install.d.ts +8 -0
- package/dist/packages/agent-harness-opentui/src/install.js +10 -0
- package/dist/packages/agent-harness-opentui/src/semantic.js +12 -10
- package/dist/packages/agent-harness-opentui/src/visual-capture.d.ts +56 -0
- package/dist/packages/agent-harness-opentui/src/visual-capture.js +103 -0
- package/dist/src/agent-harness/mock-model.d.ts +28 -0
- package/dist/src/agent-harness/mock-model.js +63 -1
- package/dist/src/agent-harness/test-spawn.js +31 -0
- package/dist/src/cli/config/screen-providers.js +1 -1
- package/dist/src/cli/cost-forensics.d.ts +10 -0
- package/dist/src/cli/cost-forensics.js +18 -3
- package/dist/src/cli/keys-bundle.d.ts +1 -1
- package/dist/src/cli/keys-bundle.js +1 -1
- package/dist/src/cli/keys.d.ts +2 -2
- package/dist/src/cli/keys.js +19 -81
- package/dist/src/council/clarifier.d.ts +28 -2
- package/dist/src/council/clarifier.js +81 -15
- package/dist/src/council/context.js +49 -15
- package/dist/src/council/debate-checkpoint.d.ts +129 -0
- package/dist/src/council/debate-checkpoint.js +176 -0
- package/dist/src/council/debate-planner.js +51 -3
- package/dist/src/council/debate-summary.d.ts +25 -0
- package/dist/src/council/debate-summary.js +85 -0
- package/dist/src/council/debate.d.ts +169 -2
- package/dist/src/council/debate.js +1210 -134
- package/dist/src/council/index.d.ts +85 -1
- package/dist/src/council/index.js +634 -196
- package/dist/src/council/leader.d.ts +26 -0
- package/dist/src/council/leader.js +150 -9
- package/dist/src/council/llm.d.ts +32 -0
- package/dist/src/council/llm.js +231 -38
- package/dist/src/council/panel-select.d.ts +30 -0
- package/dist/src/council/panel-select.js +72 -0
- package/dist/src/council/planner.js +23 -0
- package/dist/src/council/preflight.d.ts +7 -0
- package/dist/src/council/preflight.js +14 -2
- package/dist/src/council/prompts.d.ts +30 -3
- package/dist/src/council/prompts.js +234 -64
- package/dist/src/council/stance-recall.d.ts +42 -0
- package/dist/src/council/stance-recall.js +57 -0
- package/dist/src/council/strip-think.d.ts +17 -0
- package/dist/src/council/strip-think.js +33 -0
- package/dist/src/council/types.d.ts +128 -0
- package/dist/src/ee/artifact-cache.d.ts +16 -0
- package/dist/src/ee/artifact-cache.js +32 -0
- package/dist/src/ee/auth.d.ts +1 -0
- package/dist/src/ee/auth.js +15 -2
- package/dist/src/ee/bridge.d.ts +10 -0
- package/dist/src/ee/bridge.js +58 -0
- package/dist/src/ee/client.js +81 -18
- package/dist/src/ee/export-transcripts.d.ts +1 -0
- package/dist/src/ee/export-transcripts.js +8 -10
- package/dist/src/ee/extract-session.js +29 -0
- package/dist/src/ee/extract-style.d.ts +58 -0
- package/dist/src/ee/extract-style.js +270 -0
- package/dist/src/ee/recall-ledger.d.ts +9 -0
- package/dist/src/ee/recall-ledger.js +3 -0
- package/dist/src/ee/scope.d.ts +1 -0
- package/dist/src/ee/scope.js +26 -1
- package/dist/src/ee/search.d.ts +7 -0
- package/dist/src/ee/search.js +24 -0
- package/dist/src/ee/transcript-emit.js +2 -0
- package/dist/src/ee/types.d.ts +22 -0
- package/dist/src/ee/who-am-i-brain.d.ts +35 -0
- package/dist/src/ee/who-am-i-brain.js +220 -0
- package/dist/src/ee/who-am-i.d.ts +10 -3
- package/dist/src/ee/who-am-i.js +12 -0
- package/dist/src/ee/workflow-event.d.ts +48 -0
- package/dist/src/ee/workflow-event.js +81 -0
- package/dist/src/flow/compaction/compress.d.ts +3 -3
- package/dist/src/flow/compaction/compress.js +45 -8
- package/dist/src/flow/compaction/extract.d.ts +4 -7
- package/dist/src/flow/compaction/extract.js +50 -10
- package/dist/src/flow/compaction/index.d.ts +13 -1
- package/dist/src/flow/compaction/index.js +70 -3
- package/dist/src/flow/compaction/input-guard.d.ts +24 -0
- package/dist/src/flow/compaction/input-guard.js +43 -0
- package/dist/src/flow/fold-planning.d.ts +36 -0
- package/dist/src/flow/fold-planning.js +83 -0
- package/dist/src/flow/hierarchy.d.ts +146 -0
- package/dist/src/flow/hierarchy.js +427 -0
- package/dist/src/flow/index.d.ts +1 -0
- package/dist/src/flow/index.js +2 -0
- package/dist/src/flow/run-artifacts.d.ts +102 -0
- package/dist/src/flow/run-artifacts.js +208 -0
- package/dist/src/generated/version.d.ts +1 -1
- package/dist/src/generated/version.js +1 -1
- package/dist/src/gsd/assessment-schema.d.ts +44 -0
- package/dist/src/gsd/assessment-schema.js +134 -0
- package/dist/src/gsd/capability-registry.d.ts +45 -0
- package/dist/src/gsd/capability-registry.js +337 -0
- package/dist/src/gsd/complexity-assessor.d.ts +39 -0
- package/dist/src/gsd/complexity-assessor.js +152 -0
- package/dist/src/gsd/config-bridge.d.ts +7 -0
- package/dist/src/gsd/config-bridge.js +114 -0
- package/dist/src/gsd/config-loader.d.ts +27 -0
- package/dist/src/gsd/config-loader.js +50 -0
- package/dist/src/gsd/council-context.d.ts +44 -0
- package/dist/src/gsd/council-context.js +114 -0
- package/dist/src/gsd/ee-closure.d.ts +28 -0
- package/dist/src/gsd/ee-closure.js +49 -0
- package/dist/src/gsd/flags.d.ts +55 -0
- package/dist/src/gsd/flags.js +83 -0
- package/dist/src/gsd/gsd-dispatch.d.ts +58 -0
- package/dist/src/gsd/gsd-dispatch.js +131 -0
- package/dist/src/gsd/gsd-runtime.d.ts +22 -0
- package/dist/src/gsd/gsd-runtime.js +37 -0
- package/dist/src/gsd/host-adapter.d.ts +11 -0
- package/dist/src/gsd/host-adapter.js +29 -0
- package/dist/src/gsd/index.d.ts +24 -1
- package/dist/src/gsd/index.js +27 -0
- package/dist/src/gsd/loop-host-contract.d.ts +21 -0
- package/dist/src/gsd/loop-host-contract.js +39 -0
- package/dist/src/gsd/loop-host.d.ts +69 -0
- package/dist/src/gsd/loop-host.js +245 -0
- package/dist/src/gsd/loop-resolver.d.ts +36 -0
- package/dist/src/gsd/loop-resolver.js +79 -0
- package/dist/src/gsd/model-tier.d.ts +13 -0
- package/dist/src/gsd/model-tier.js +45 -0
- package/dist/src/gsd/mutation-gate.d.ts +16 -0
- package/dist/src/gsd/mutation-gate.js +41 -0
- package/dist/src/gsd/native-roadmap.d.ts +89 -0
- package/dist/src/gsd/native-roadmap.js +343 -0
- package/dist/src/gsd/native-state.d.ts +47 -0
- package/dist/src/gsd/native-state.js +220 -0
- package/dist/src/gsd/paths.d.ts +23 -0
- package/dist/src/gsd/paths.js +66 -0
- package/dist/src/gsd/phase-dag.d.ts +12 -0
- package/dist/src/gsd/phase-dag.js +94 -0
- package/dist/src/gsd/phase-sync.d.ts +42 -0
- package/dist/src/gsd/phase-sync.js +321 -0
- package/dist/src/gsd/pil-gate-context.d.ts +13 -0
- package/dist/src/gsd/pil-gate-context.js +64 -0
- package/dist/src/gsd/pil-gate-critic.d.ts +19 -0
- package/dist/src/gsd/pil-gate-critic.js +74 -0
- package/dist/src/gsd/plan-council-prompts.d.ts +25 -0
- package/dist/src/gsd/plan-council-prompts.js +79 -0
- package/dist/src/gsd/plan-council.d.ts +44 -0
- package/dist/src/gsd/plan-council.js +251 -0
- package/dist/src/gsd/plan-gate-vocabulary.d.ts +40 -0
- package/dist/src/gsd/plan-gate-vocabulary.js +64 -0
- package/dist/src/gsd/product-workspace.d.ts +13 -0
- package/dist/src/gsd/product-workspace.js +124 -0
- package/dist/src/gsd/ship-bridge.d.ts +25 -0
- package/dist/src/gsd/ship-bridge.js +65 -0
- package/dist/src/gsd/state-document.d.ts +40 -0
- package/dist/src/gsd/state-document.js +163 -0
- package/dist/src/gsd/verdict-schema.d.ts +39 -0
- package/dist/src/gsd/verdict-schema.js +144 -0
- package/dist/src/gsd/verify-context.d.ts +22 -0
- package/dist/src/gsd/verify-context.js +27 -0
- package/dist/src/gsd/verify-council-prompts.d.ts +19 -0
- package/dist/src/gsd/verify-council-prompts.js +85 -0
- package/dist/src/gsd/verify-council.d.ts +25 -0
- package/dist/src/gsd/verify-council.js +119 -0
- package/dist/src/gsd/verify-gate-vocabulary.d.ts +25 -0
- package/dist/src/gsd/verify-gate-vocabulary.js +46 -0
- package/dist/src/gsd/workflow-engine.d.ts +60 -0
- package/dist/src/gsd/workflow-engine.js +207 -0
- package/dist/src/gsd/workflow-tools.d.ts +13 -0
- package/dist/src/gsd/workflow-tools.js +277 -0
- package/dist/src/hooks/index.js +1 -1
- package/dist/src/index.js +44 -11
- package/dist/src/maintain/pr-builder.js +23 -13
- package/dist/src/mcp/auto-setup.js +57 -32
- package/dist/src/mcp/client-pool.js +1 -1
- package/dist/src/mcp/ee-tools.js +1 -0
- package/dist/src/mcp/research-onboarding.js +8 -7
- package/dist/src/mcp/runtime.js +34 -2
- package/dist/src/models/catalog-client.d.ts +87 -0
- package/dist/src/models/catalog-client.js +105 -38
- package/dist/src/models/catalog.json +528 -265
- package/dist/src/models/registry.d.ts +22 -7
- package/dist/src/models/registry.js +73 -10
- package/dist/src/ops/doctor.js +1 -1
- package/dist/src/orchestrator/auto-commit.js +1 -1
- package/dist/src/orchestrator/batch-turn-runner.js +2 -2
- package/dist/src/orchestrator/cache-prefix.d.ts +67 -0
- package/dist/src/orchestrator/cache-prefix.js +83 -0
- package/dist/src/orchestrator/compact-request.d.ts +32 -0
- package/dist/src/orchestrator/compact-request.js +41 -0
- package/dist/src/orchestrator/compaction.d.ts +10 -0
- package/dist/src/orchestrator/compaction.js +27 -7
- package/dist/src/orchestrator/council-manager.d.ts +12 -3
- package/dist/src/orchestrator/council-manager.js +65 -24
- package/dist/src/orchestrator/cross-turn-dedup.d.ts +6 -0
- package/dist/src/orchestrator/cross-turn-dedup.js +22 -1
- package/dist/src/orchestrator/error-utils.d.ts +29 -0
- package/dist/src/orchestrator/error-utils.js +132 -24
- package/dist/src/orchestrator/grounding-check.js +39 -1
- package/dist/src/orchestrator/message-processor.js +242 -33
- package/dist/src/orchestrator/orchestrator.d.ts +39 -3
- package/dist/src/orchestrator/orchestrator.js +651 -102
- package/dist/src/orchestrator/preprocessor.js +1 -1
- package/dist/src/orchestrator/proactive-compact-detector.d.ts +26 -0
- package/dist/src/orchestrator/proactive-compact-detector.js +36 -0
- package/dist/src/orchestrator/prompts.js +17 -17
- package/dist/src/orchestrator/reactive-delegation.d.ts +39 -0
- package/dist/src/orchestrator/reactive-delegation.js +59 -0
- package/dist/src/orchestrator/retry-classifier.d.ts +2 -1
- package/dist/src/orchestrator/retry-classifier.js +46 -2
- package/dist/src/orchestrator/safety-intercept.d.ts +45 -0
- package/dist/src/orchestrator/safety-intercept.js +55 -0
- package/dist/src/orchestrator/scope-reminder.js +1 -1
- package/dist/src/orchestrator/session-experience.d.ts +2 -1
- package/dist/src/orchestrator/session-experience.js +2 -1
- package/dist/src/orchestrator/should-run-gate.d.ts +5 -0
- package/dist/src/orchestrator/should-run-gate.js +18 -0
- package/dist/src/orchestrator/stall-watchdog.d.ts +24 -3
- package/dist/src/orchestrator/stall-watchdog.js +47 -13
- package/dist/src/orchestrator/stream-runner.js +62 -29
- package/dist/src/orchestrator/sub-agent-cap.d.ts +13 -1
- package/dist/src/orchestrator/sub-agent-cap.js +16 -1
- package/dist/src/orchestrator/sub-agent-model-tier.d.ts +13 -1
- package/dist/src/orchestrator/sub-agent-model-tier.js +16 -2
- package/dist/src/orchestrator/subagent-compactor.d.ts +53 -1
- package/dist/src/orchestrator/subagent-compactor.js +126 -15
- package/dist/src/orchestrator/tool-engine.d.ts +22 -0
- package/dist/src/orchestrator/tool-engine.js +620 -56
- package/dist/src/orchestrator/tool-limit-auto-recover.d.ts +22 -0
- package/dist/src/orchestrator/tool-limit-auto-recover.js +30 -0
- package/dist/src/orchestrator/turn-runner-deps.d.ts +19 -0
- package/dist/src/orchestrator/turn-watchdog.d.ts +37 -0
- package/dist/src/orchestrator/turn-watchdog.js +55 -0
- package/dist/src/pil/agent-operating-contract.d.ts +1 -1
- package/dist/src/pil/agent-operating-contract.js +1 -1
- package/dist/src/pil/cheap-model-playbook.d.ts +1 -1
- package/dist/src/pil/cheap-model-playbook.js +5 -1
- package/dist/src/pil/cheap-model-workbooks.d.ts +1 -1
- package/dist/src/pil/cheap-model-workbooks.js +1 -1
- package/dist/src/pil/discovery-types.d.ts +1 -0
- package/dist/src/pil/discovery.js +16 -11
- package/dist/src/pil/layer1-intent.d.ts +18 -6
- package/dist/src/pil/layer1-intent.js +66 -757
- package/dist/src/pil/layer15-context-scan.js +15 -1
- package/dist/src/pil/layer3-ee-injection.js +23 -8
- package/dist/src/pil/layer4-gsd.js +69 -16
- package/dist/src/pil/layer5-context.js +7 -3
- package/dist/src/pil/layer6-output.d.ts +23 -0
- package/dist/src/pil/layer6-output.js +5 -1
- package/dist/src/pil/llm-classify.d.ts +33 -2
- package/dist/src/pil/llm-classify.js +123 -131
- package/dist/src/pil/native-capabilities-workbook.d.ts +1 -1
- package/dist/src/pil/native-capabilities-workbook.js +1 -0
- package/dist/src/pil/pipeline.js +34 -2
- package/dist/src/pil/response-tools.js +5 -3
- package/dist/src/pil/schema.d.ts +1 -0
- package/dist/src/pil/schema.js +2 -0
- package/dist/src/pil/types.d.ts +18 -0
- package/dist/src/playbook/directives.d.ts +4 -0
- package/dist/src/playbook/directives.js +17 -5
- package/dist/src/product-loop/backlog-builder.d.ts +14 -1
- package/dist/src/product-loop/backlog-builder.js +30 -6
- package/dist/src/product-loop/discovery-context-format.js +3 -1
- package/dist/src/product-loop/discovery-ecosystem.js +4 -1
- package/dist/src/product-loop/discovery-interview.js +32 -3
- package/dist/src/product-loop/discovery-schema.js +5 -1
- package/dist/src/product-loop/ideal-trace.d.ts +7 -0
- package/dist/src/product-loop/ideal-trace.js +64 -0
- package/dist/src/product-loop/index.d.ts +13 -1
- package/dist/src/product-loop/index.js +333 -52
- package/dist/src/product-loop/loop-driver.d.ts +7 -0
- package/dist/src/product-loop/loop-driver.js +310 -99
- package/dist/src/product-loop/phase-plan.d.ts +5 -0
- package/dist/src/product-loop/phase-plan.js +39 -2
- package/dist/src/product-loop/phase-runner.js +9 -1
- package/dist/src/product-loop/sprint-runner.d.ts +111 -0
- package/dist/src/product-loop/sprint-runner.js +559 -16
- package/dist/src/product-loop/types.d.ts +36 -5
- package/dist/src/providers/adapter.d.ts +1 -1
- package/dist/src/providers/adapter.js +3 -4
- package/dist/src/providers/auth/browser-flow.d.ts +1 -1
- package/dist/src/providers/auth/browser-flow.js +1 -1
- package/dist/src/providers/auth/openai-oauth.js +1 -1
- package/dist/src/providers/auth/registry.js +0 -34
- package/dist/src/providers/auth/token-store.js +4 -1
- package/dist/src/providers/auth/types.d.ts +1 -1
- package/dist/src/providers/auth/types.js +1 -1
- package/dist/src/providers/capabilities.d.ts +24 -5
- package/dist/src/providers/capabilities.js +42 -24
- package/dist/src/providers/endpoints.d.ts +2 -2
- package/dist/src/providers/endpoints.js +11 -10
- package/dist/src/providers/keychain.d.ts +1 -1
- package/dist/src/providers/keychain.js +7 -9
- package/dist/src/providers/mcp-vision-bridge.js +56 -146
- package/dist/src/providers/openai-compatible.js +8 -1
- package/dist/src/providers/pricing.d.ts +2 -2
- package/dist/src/providers/pricing.js +3 -13
- package/dist/src/providers/runtime.d.ts +27 -2
- package/dist/src/providers/runtime.js +78 -15
- package/dist/src/providers/strategies/base.strategy.d.ts +16 -0
- package/dist/src/providers/strategies/base.strategy.js +24 -1
- package/dist/src/providers/strategies/{siliconflow.strategy.d.ts → opencode-go.strategy.d.ts} +3 -3
- package/dist/src/providers/strategies/opencode-go.strategy.js +83 -0
- package/dist/src/providers/strategies/registry.js +4 -4
- package/dist/src/providers/strategies/thinking-mode.d.ts +109 -0
- package/dist/src/providers/strategies/thinking-mode.js +280 -1
- package/dist/src/providers/strategies/zai.strategy.d.ts +14 -0
- package/dist/src/providers/strategies/zai.strategy.js +44 -0
- package/dist/src/providers/types.d.ts +5 -6
- package/dist/src/providers/types.js +2 -2
- package/dist/src/providers/vision-backend.d.ts +47 -0
- package/dist/src/providers/vision-backend.js +258 -0
- package/dist/src/providers/vision-proxy.d.ts +22 -9
- package/dist/src/providers/vision-proxy.js +63 -132
- package/dist/src/providers/wire-debug.js +95 -0
- package/dist/src/router/decide.d.ts +13 -0
- package/dist/src/router/decide.js +138 -36
- package/dist/src/router/peak-hour.d.ts +38 -0
- package/dist/src/router/peak-hour.js +107 -0
- package/dist/src/router/step-router.js +3 -2
- package/dist/src/router/warm.js +4 -5
- package/dist/src/scaffold/continuation-prompt.d.ts +11 -0
- package/dist/src/scaffold/continuation-prompt.js +26 -0
- package/dist/src/scaffold/point-to-existing.d.ts +21 -0
- package/dist/src/scaffold/point-to-existing.js +25 -0
- package/dist/src/self-qa/agentic-loop.js +3 -3
- package/dist/src/{ui/state → state}/active-run.d.ts +19 -0
- package/dist/src/{ui/state → state}/active-run.js +21 -0
- package/dist/src/{ui/status-bar/store.d.ts → state/status-bar-store.d.ts} +4 -1
- package/dist/src/{ui/status-bar/store.js → state/status-bar-store.js} +12 -9
- package/dist/src/state/turn-trace.d.ts +43 -0
- package/dist/src/state/turn-trace.js +32 -0
- package/dist/src/storage/db.js +2 -1
- package/dist/src/storage/index.d.ts +1 -1
- package/dist/src/storage/index.js +1 -1
- package/dist/src/storage/interaction-log.d.ts +1 -1
- package/dist/src/storage/migrations.js +71 -1
- package/dist/src/storage/sessions.d.ts +28 -10
- package/dist/src/storage/sessions.js +78 -21
- package/dist/src/storage/transcript-view.js +1 -1
- package/dist/src/storage/transcript.d.ts +51 -0
- package/dist/src/storage/transcript.js +284 -13
- package/dist/src/tools/file.d.ts +15 -0
- package/dist/src/tools/file.js +32 -0
- package/dist/src/tools/native-tools.js +5 -0
- package/dist/src/tools/registry.d.ts +3 -0
- package/dist/src/tools/registry.js +460 -22
- package/dist/src/tools/research.d.ts +29 -0
- package/dist/src/tools/research.js +233 -0
- package/dist/src/types/index.d.ts +118 -3
- package/dist/src/ui/app.js +0 -0
- package/dist/src/ui/cards/product-status-card.js +1 -1
- package/dist/src/ui/components/bubble-body-guard.d.ts +27 -0
- package/dist/src/ui/components/bubble-body-guard.js +50 -0
- package/dist/src/ui/components/context-rail.d.ts +26 -0
- package/dist/src/ui/components/context-rail.js +33 -0
- package/dist/src/ui/components/council-conclusion-card.d.ts +73 -0
- package/dist/src/ui/components/council-conclusion-card.js +420 -0
- package/dist/src/ui/components/council-debate-pill.d.ts +36 -0
- package/dist/src/ui/components/council-debate-pill.js +34 -0
- package/dist/src/ui/components/council-info-card.js +2 -2
- package/dist/src/ui/components/council-leader-bubble.d.ts +10 -3
- package/dist/src/ui/components/council-leader-bubble.js +21 -11
- package/dist/src/ui/components/council-message-bubble.d.ts +15 -2
- package/dist/src/ui/components/council-message-bubble.js +16 -15
- package/dist/src/ui/components/council-phase-timeline.d.ts +9 -1
- package/dist/src/ui/components/council-phase-timeline.js +49 -15
- package/dist/src/ui/components/council-placeholder-bubble.d.ts +16 -9
- package/dist/src/ui/components/council-placeholder-bubble.js +32 -29
- package/dist/src/ui/components/council-question-card.js +12 -12
- package/dist/src/ui/components/council-rail-rounds.d.ts +26 -0
- package/dist/src/ui/components/council-rail-rounds.js +57 -0
- package/dist/src/ui/components/council-round-group.d.ts +38 -0
- package/dist/src/ui/components/council-round-group.js +88 -0
- package/dist/src/ui/components/council-status-list.d.ts +3 -1
- package/dist/src/ui/components/council-status-list.js +36 -24
- package/dist/src/ui/components/council-synthesis-banner.d.ts +7 -2
- package/dist/src/ui/components/council-synthesis-banner.js +20 -5
- package/dist/src/ui/components/halt-recovery-card.js +9 -5
- package/dist/src/ui/components/jump-to-latest-pill.d.ts +11 -0
- package/dist/src/ui/components/jump-to-latest-pill.js +14 -0
- package/dist/src/ui/components/prompt-box.js +18 -16
- package/dist/src/ui/components/session-tree-card.d.ts +14 -0
- package/dist/src/ui/components/session-tree-card.js +46 -0
- package/dist/src/ui/components/slash-inline-menu.d.ts +12 -0
- package/dist/src/ui/components/slash-inline-menu.js +26 -5
- package/dist/src/ui/components/task-list-panel.d.ts +14 -1
- package/dist/src/ui/components/task-list-panel.js +22 -2
- package/dist/src/ui/containers/modals-layer.d.ts +2 -1
- package/dist/src/ui/hooks/use-session-picker.d.ts +3 -3
- package/dist/src/ui/mcp-modal.js +2 -4
- package/dist/src/ui/modals/api-key-modal.js +1 -1
- package/dist/src/ui/modals/connect-modal.js +4 -3
- package/dist/src/ui/modals/session-picker-modal.d.ts +2 -2
- package/dist/src/ui/modals/session-picker-modal.js +3 -5
- package/dist/src/ui/picker-providers.d.ts +1 -1
- package/dist/src/ui/picker-providers.js +1 -1
- package/dist/src/ui/primitives/index.d.ts +1 -0
- package/dist/src/ui/primitives/index.js +2 -0
- package/dist/src/ui/primitives/semantic-primitives.d.ts +76 -0
- package/dist/src/ui/primitives/semantic-primitives.js +81 -0
- package/dist/src/ui/slash/compact.js +5 -7
- package/dist/src/ui/slash/cost.js +1 -1
- package/dist/src/ui/slash/council.js +19 -1
- package/dist/src/ui/slash/debug.d.ts +3 -31
- package/dist/src/ui/slash/debug.js +9 -20
- package/dist/src/ui/slash/ideal.d.ts +6 -2
- package/dist/src/ui/slash/ideal.js +97 -7
- package/dist/src/ui/slash/menu-items.d.ts +7 -0
- package/dist/src/ui/slash/menu-items.js +12 -18
- package/dist/src/ui/slash/registry.d.ts +2 -0
- package/dist/src/ui/slash/registry.js +4 -0
- package/dist/src/ui/status-bar/cache-hit.d.ts +6 -0
- package/dist/src/ui/status-bar/cache-hit.js +9 -0
- package/dist/src/ui/status-bar/index.d.ts +1 -1
- package/dist/src/ui/status-bar/index.js +7 -3
- package/dist/src/ui/status-bar/usd-meter.d.ts +5 -4
- package/dist/src/ui/status-bar/usd-meter.js +6 -4
- package/dist/src/ui/theme.d.ts +1 -0
- package/dist/src/ui/theme.js +2 -0
- package/dist/src/ui/types.d.ts +7 -0
- package/dist/src/ui/use-app-logic.js +0 -0
- package/dist/src/ui/utils/format.d.ts +14 -0
- package/dist/src/ui/utils/format.js +23 -3
- package/dist/src/usage/downgrade.js +2 -2
- package/dist/src/usage/product-ledger.js +2 -2
- package/dist/src/utils/install-manager.js +2 -1
- package/dist/src/utils/logger.js +2 -2
- package/dist/src/utils/permission-mode.js +5 -3
- package/dist/src/utils/redactor.js +1 -1
- package/dist/src/utils/settings.d.ts +153 -5
- package/dist/src/utils/settings.js +233 -29
- package/dist/src/utils/visible-retry.d.ts +11 -0
- package/dist/src/utils/visible-retry.js +10 -1
- package/dist/src/verify/entrypoint.d.ts +1 -1
- package/dist/src/verify/entrypoint.js +1 -1
- package/dist/src/verify/recipes.d.ts +13 -0
- package/dist/src/verify/recipes.js +15 -0
- package/package.json +135 -132
- package/dist/src/providers/auth/gcloud.d.ts +0 -28
- package/dist/src/providers/auth/gcloud.js +0 -102
- package/dist/src/providers/auth/gemini-oauth.d.ts +0 -82
- package/dist/src/providers/auth/gemini-oauth.js +0 -472
- package/dist/src/providers/gemini.d.ts +0 -11
- package/dist/src/providers/gemini.js +0 -45
- package/dist/src/providers/siliconflow-sse-repair.d.ts +0 -58
- package/dist/src/providers/siliconflow-sse-repair.js +0 -177
- package/dist/src/providers/strategies/google.strategy.d.ts +0 -22
- package/dist/src/providers/strategies/google.strategy.js +0 -174
- package/dist/src/providers/strategies/siliconflow.strategy.js +0 -29
- package/dist/src/ui/containers/chat-feed.d.ts +0 -40
- package/dist/src/ui/containers/chat-feed.js +0 -66
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* If cap breach detected, downgrade chain overrides classifier output (ROUTE-06).
|
|
7
7
|
*/
|
|
8
8
|
import { getDefaultEEClient } from "../ee/intercept.js";
|
|
9
|
-
import {
|
|
9
|
+
import { getModelInfo, getModelsForProvider } from "../models/registry.js";
|
|
10
10
|
import { taskTypeToRole } from "../pil/task-tier-map.js";
|
|
11
11
|
import { detectProviderForModel } from "../providers/runtime.js";
|
|
12
12
|
import { ALL_PROVIDER_IDS } from "../providers/types.js";
|
|
@@ -14,9 +14,10 @@ import { downgradeChain, emitDowngrade, getDowngradeChain } from "../usage/downg
|
|
|
14
14
|
import { release, reserve } from "../usage/ledger.js";
|
|
15
15
|
import { midstreamPolicy } from "../usage/midstream.js";
|
|
16
16
|
import { CapBreachError } from "../usage/types.js";
|
|
17
|
-
import { getRoleModel, isCouncilMultiProviderPreferred, isProviderDisabled } from "../utils/settings.js";
|
|
17
|
+
import { getRoleModel, getRoutingPromoteMax, isCouncilMultiProviderPreferred, isProviderDisabled, } from "../utils/settings.js";
|
|
18
18
|
import { classify } from "./classifier/index.js";
|
|
19
19
|
import { callColdRoute } from "./cold.js";
|
|
20
|
+
import { adjustPeakHourModel, getRoutedModelByTier } from "./peak-hour.js";
|
|
20
21
|
import { isInheritProvider } from "./provider-sentinel.js";
|
|
21
22
|
import { routerStore } from "./store.js";
|
|
22
23
|
import { callWarmRoute } from "./warm.js";
|
|
@@ -75,6 +76,16 @@ function buildRouteContext(cwd, pil) {
|
|
|
75
76
|
ctx.filesTouched = pil.filesTouched;
|
|
76
77
|
if (pil?.mode)
|
|
77
78
|
ctx.mode = pil.mode;
|
|
79
|
+
if (pil?.turnIndex !== undefined)
|
|
80
|
+
ctx.turnIndex = pil.turnIndex;
|
|
81
|
+
if (pil?.messageCount !== undefined)
|
|
82
|
+
ctx.messageCount = pil.messageCount;
|
|
83
|
+
if (pil?.compactionCount !== undefined)
|
|
84
|
+
ctx.compactionCount = pil.compactionCount;
|
|
85
|
+
if (pil?.totalSavedTokens !== undefined)
|
|
86
|
+
ctx.totalSavedTokens = pil.totalSavedTokens;
|
|
87
|
+
if (pil?.compactionSummary)
|
|
88
|
+
ctx.compactionSummary = pil.compactionSummary;
|
|
78
89
|
return ctx;
|
|
79
90
|
}
|
|
80
91
|
// ─── Disabled-provider guard: fallback providers ────────────────────────────
|
|
@@ -90,7 +101,7 @@ function resolveEffectiveDefaults(opts) {
|
|
|
90
101
|
}
|
|
91
102
|
for (const p of FALLBACK_PROVIDERS) {
|
|
92
103
|
if (!isProviderDisabled(p)) {
|
|
93
|
-
const m =
|
|
104
|
+
const m = getRoutedModelByTier("balanced", p);
|
|
94
105
|
// Guard: getModelByTier may return a model from a different provider
|
|
95
106
|
// when the preferred provider has no model for the requested tier.
|
|
96
107
|
if (m && m.provider === p)
|
|
@@ -114,7 +125,7 @@ function resolveTierModel(tier, defaultProvider) {
|
|
|
114
125
|
}
|
|
115
126
|
for (const p of FALLBACK_PROVIDERS) {
|
|
116
127
|
if (!isProviderDisabled(p)) {
|
|
117
|
-
const m =
|
|
128
|
+
const m = getRoutedModelByTier(tier, p);
|
|
118
129
|
// Guard: getModelByTier may return a model from a different provider
|
|
119
130
|
// when the preferred provider has no model for the requested tier.
|
|
120
131
|
if (m && m.provider === p)
|
|
@@ -126,6 +137,17 @@ function resolveTierModel(tier, defaultProvider) {
|
|
|
126
137
|
}
|
|
127
138
|
return undefined;
|
|
128
139
|
}
|
|
140
|
+
function applyPeakHourRoute(dec) {
|
|
141
|
+
const adj = adjustPeakHourModel(dec.model);
|
|
142
|
+
if (!adj.adjusted)
|
|
143
|
+
return dec;
|
|
144
|
+
return {
|
|
145
|
+
...dec,
|
|
146
|
+
model: adj.modelId,
|
|
147
|
+
provider: adj.provider,
|
|
148
|
+
reason: `${dec.reason}|${adj.reason}`,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
129
151
|
// ─── Provider constraint: never route to a provider the user lacks a key for ─
|
|
130
152
|
function constrainToProvider(decision, opts) {
|
|
131
153
|
// Inherit-sentinel: warm path may emit an empty provider to signal
|
|
@@ -134,11 +156,11 @@ function constrainToProvider(decision, opts) {
|
|
|
134
156
|
if (isInheritProvider(decision.provider)) {
|
|
135
157
|
const resolved = detectProviderForModel(decision.model);
|
|
136
158
|
if (resolved && !isProviderDisabled(resolved))
|
|
137
|
-
return decision;
|
|
159
|
+
return applyPeakHourRoute(decision);
|
|
138
160
|
if (resolved && isProviderDisabled(resolved)) {
|
|
139
161
|
return constrainToProvider({ ...decision, provider: resolved }, opts);
|
|
140
162
|
}
|
|
141
|
-
return decision;
|
|
163
|
+
return applyPeakHourRoute(decision);
|
|
142
164
|
}
|
|
143
165
|
// Provider-only UX: the user enables N providers and pins one as default.
|
|
144
166
|
// Routing rules:
|
|
@@ -150,7 +172,7 @@ function constrainToProvider(decision, opts) {
|
|
|
150
172
|
// surface a config error elsewhere).
|
|
151
173
|
const decisionDisabled = isProviderDisabled(decision.provider);
|
|
152
174
|
if (!decisionDisabled) {
|
|
153
|
-
return decision;
|
|
175
|
+
return applyPeakHourRoute(decision);
|
|
154
176
|
}
|
|
155
177
|
if (isProviderDisabled(opts.defaultProvider)) {
|
|
156
178
|
return {
|
|
@@ -158,7 +180,7 @@ function constrainToProvider(decision, opts) {
|
|
|
158
180
|
reason: `${decision.reason}|provider-not-constrained(default-also-disabled)`,
|
|
159
181
|
};
|
|
160
182
|
}
|
|
161
|
-
const sameProviderModel =
|
|
183
|
+
const sameProviderModel = getRoutedModelByTier(decision.tier === "hot" ? "fast" : decision.tier === "cold" ? "premium" : "balanced", opts.defaultProvider);
|
|
162
184
|
if (sameProviderModel && sameProviderModel.provider !== opts.defaultProvider) {
|
|
163
185
|
return {
|
|
164
186
|
...decision,
|
|
@@ -167,12 +189,12 @@ function constrainToProvider(decision, opts) {
|
|
|
167
189
|
reason: `${decision.reason}|provider-constrained(forced-default)`,
|
|
168
190
|
};
|
|
169
191
|
}
|
|
170
|
-
return {
|
|
192
|
+
return applyPeakHourRoute({
|
|
171
193
|
...decision,
|
|
172
194
|
model: sameProviderModel?.id ?? opts.defaultModel,
|
|
173
195
|
provider: sameProviderModel?.provider ?? opts.defaultProvider,
|
|
174
196
|
reason: `${decision.reason}|provider-constrained`,
|
|
175
|
-
};
|
|
197
|
+
});
|
|
176
198
|
}
|
|
177
199
|
// ─── Route feedback (HTTP path) ─────────────────────────────────────────────
|
|
178
200
|
/**
|
|
@@ -194,12 +216,78 @@ export function reportRouteOutcome(taskHash, outcome, duration) {
|
|
|
194
216
|
duration: duration ?? null,
|
|
195
217
|
});
|
|
196
218
|
}
|
|
219
|
+
// ─── Tier-promotion cap (cost-ceiling guard) ───────────────────────────────
|
|
220
|
+
const TIER_RANK = {
|
|
221
|
+
fast: 0,
|
|
222
|
+
balanced: 1,
|
|
223
|
+
premium: 2,
|
|
224
|
+
};
|
|
225
|
+
/**
|
|
226
|
+
* Enforce the user's tier-promotion ceiling. The session default model is the
|
|
227
|
+
* cost ceiling: the router may downgrade per turn but may not silently promote
|
|
228
|
+
* beyond `routingPromoteMax` (default "balanced"). See settings.ts.
|
|
229
|
+
*
|
|
230
|
+
* When the decision would promote, clamp down to the max allowed tier on the
|
|
231
|
+
* SAME provider. If no same-provider model exists at the ceiling tier, fall
|
|
232
|
+
* back to the session default model (the user's explicit pick). The clamp is
|
|
233
|
+
* skipped for: the `"any"` opt-in, the role path (explicit user roleModels
|
|
234
|
+
* config is itself the opt-in), cap-halt decisions, and provider-constrained
|
|
235
|
+
* disabled-provider recoveries (those already move toward the default).
|
|
236
|
+
*/
|
|
237
|
+
function applyPromotionCap(dec, defaultModel) {
|
|
238
|
+
if (dec.model === "HALT" || dec.model === defaultModel)
|
|
239
|
+
return dec;
|
|
240
|
+
const cap = getRoutingPromoteMax();
|
|
241
|
+
if (cap === "any")
|
|
242
|
+
return dec;
|
|
243
|
+
const capRank = cap === "off" ? null : TIER_RANK[cap]; // "balanced" → 1
|
|
244
|
+
const defaultTier = getModelInfo(defaultModel)?.tier;
|
|
245
|
+
// "off" means ceiling = the default model's own tier.
|
|
246
|
+
const maxAllowedRank = capRank === null ? (defaultTier ? TIER_RANK[defaultTier] : 0) : capRank;
|
|
247
|
+
const decInfo = getModelInfo(dec.model);
|
|
248
|
+
const decTier = decInfo?.tier;
|
|
249
|
+
if (!decTier || TIER_RANK[decTier] <= maxAllowedRank)
|
|
250
|
+
return dec;
|
|
251
|
+
// Promotion exceeds the ceiling — clamp DOWN to the max allowed tier on the
|
|
252
|
+
// same provider. Walk down from the ceiling so we pick the highest permitted.
|
|
253
|
+
const provider = decInfo?.provider ?? detectProviderForModel(dec.model);
|
|
254
|
+
const targetTiers = capRank === null
|
|
255
|
+
? defaultTier
|
|
256
|
+
? [defaultTier]
|
|
257
|
+
: ["fast"]
|
|
258
|
+
: maxAllowedRank >= 1
|
|
259
|
+
? ["balanced", "fast"]
|
|
260
|
+
: ["fast"];
|
|
261
|
+
for (const t of targetTiers) {
|
|
262
|
+
if (TIER_RANK[t] > maxAllowedRank)
|
|
263
|
+
continue;
|
|
264
|
+
const m = getRoutedModelByTier(t, provider);
|
|
265
|
+
if (m && m.provider === provider) {
|
|
266
|
+
return {
|
|
267
|
+
...dec,
|
|
268
|
+
model: m.id,
|
|
269
|
+
reason: `${dec.reason} | promo-cap(${decTier}→${t})`,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
// No cheaper model on the same provider — fall back to the session default.
|
|
274
|
+
return {
|
|
275
|
+
...dec,
|
|
276
|
+
model: defaultModel,
|
|
277
|
+
reason: `${dec.reason} | promo-cap(${decTier}→default:${defaultModel})`,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
197
280
|
/**
|
|
198
281
|
* Apply cap-check to a RouteDecision. Walks the downgrade chain if
|
|
199
282
|
* the reservation would breach the cap. Returns the (possibly downgraded) decision.
|
|
283
|
+
*
|
|
284
|
+
* `opts` carries both the ledger homeOverride and the session defaultModel used
|
|
285
|
+
* by the promotion cap. `exempt` skips the promotion cap — used only for the
|
|
286
|
+
* role path, where the user's explicit roleModels config is itself the opt-in.
|
|
200
287
|
*/
|
|
201
|
-
async function capCheck(dec,
|
|
202
|
-
let current = { ...dec };
|
|
288
|
+
async function capCheck(dec, opts, exempt) {
|
|
289
|
+
let current = exempt ? { ...dec } : applyPromotionCap({ ...dec }, opts.defaultModel);
|
|
290
|
+
const homeOverride = opts.homeOverride;
|
|
203
291
|
let attempts = 0;
|
|
204
292
|
while (attempts++ < getDowngradeChain().length) {
|
|
205
293
|
// If midstream policy already refuses, halt immediately
|
|
@@ -305,14 +393,15 @@ export async function decide(prompt, opts) {
|
|
|
305
393
|
providerDisabled: isProviderDisabled(provider),
|
|
306
394
|
multiProviderPreferred: isCouncilMultiProviderPreferred(),
|
|
307
395
|
})) {
|
|
396
|
+
const peak = adjustPeakHourModel(roleModelId);
|
|
308
397
|
const d = {
|
|
309
398
|
tier: "hot",
|
|
310
|
-
model:
|
|
311
|
-
provider,
|
|
312
|
-
reason: `role:${role}→${roleModelId}`,
|
|
399
|
+
model: peak.modelId,
|
|
400
|
+
provider: peak.provider,
|
|
401
|
+
reason: peak.adjusted ? `role:${role}→${peak.modelId}|${peak.reason}` : `role:${role}→${roleModelId}`,
|
|
313
402
|
source: "role",
|
|
314
403
|
};
|
|
315
|
-
const checked = await capCheck(d, opts
|
|
404
|
+
const checked = await capCheck(d, opts, /* exempt */ true);
|
|
316
405
|
routerStore.setState({ tier: checked.tier, lastDecision: checked, taskHash: null, source: "role" });
|
|
317
406
|
if (cacheKey && !checked.cap_overridden)
|
|
318
407
|
setCachedRoute(cacheKey, checked);
|
|
@@ -328,7 +417,7 @@ export async function decide(prompt, opts) {
|
|
|
328
417
|
if (pilTier && pilConf >= 0.6) {
|
|
329
418
|
// Use effective (non-disabled) provider when default is disabled
|
|
330
419
|
const effective = resolveTierModel(pilTier, opts.defaultProvider);
|
|
331
|
-
let tierModel = effective ??
|
|
420
|
+
let tierModel = effective ?? getRoutedModelByTier(pilTier, opts.defaultProvider);
|
|
332
421
|
// Guard: getModelByTier may cross to another provider when defaultProvider
|
|
333
422
|
// has no model for the requested tier. If that cross-provider is disabled,
|
|
334
423
|
// pin to the default model on the default provider instead.
|
|
@@ -338,17 +427,21 @@ export async function decide(prompt, opts) {
|
|
|
338
427
|
isProviderDisabled(tierModel.provider)) {
|
|
339
428
|
tierModel = undefined;
|
|
340
429
|
}
|
|
430
|
+
const pilModel = tierModel?.id ?? opts.defaultModel;
|
|
431
|
+
const peak = adjustPeakHourModel(pilModel);
|
|
341
432
|
const d = {
|
|
342
433
|
tier: "hot",
|
|
343
|
-
model:
|
|
344
|
-
provider: tierModel?.provider ??
|
|
345
|
-
reason:
|
|
346
|
-
? `pil:${pilTier}(${pilConf.toFixed(2)})-rerouted(disabled-default)`
|
|
347
|
-
:
|
|
434
|
+
model: peak.modelId,
|
|
435
|
+
provider: tierModel?.provider ?? peak.provider,
|
|
436
|
+
reason: peak.adjusted
|
|
437
|
+
? `${effective ? `pil:${pilTier}(${pilConf.toFixed(2)})-rerouted(disabled-default)` : `pil:${pilTier}(${pilConf.toFixed(2)})`}|${peak.reason}`
|
|
438
|
+
: effective
|
|
439
|
+
? `pil:${pilTier}(${pilConf.toFixed(2)})-rerouted(disabled-default)`
|
|
440
|
+
: `pil:${pilTier}(${pilConf.toFixed(2)})`,
|
|
348
441
|
confidence: pilConf,
|
|
349
442
|
source: "pil",
|
|
350
443
|
};
|
|
351
|
-
const checked = await capCheck(d, opts
|
|
444
|
+
const checked = await capCheck(d, opts);
|
|
352
445
|
routerStore.setState({
|
|
353
446
|
tier: checked.tier,
|
|
354
447
|
lastDecision: checked,
|
|
@@ -364,7 +457,7 @@ export async function decide(prompt, opts) {
|
|
|
364
457
|
if (c.tier === "hot") {
|
|
365
458
|
// Use effective (non-disabled) provider when default is disabled
|
|
366
459
|
const effective = c.tierHint ? resolveTierModel(c.tierHint, opts.defaultProvider) : undefined;
|
|
367
|
-
let tierModel = effective ?? (c.tierHint ?
|
|
460
|
+
let tierModel = effective ?? (c.tierHint ? getRoutedModelByTier(c.tierHint, opts.defaultProvider) : undefined);
|
|
368
461
|
// Same guard as the PIL branch above: drop cross-provider fallback when
|
|
369
462
|
// the cross-provider is disabled, so we don't switch to a provider the
|
|
370
463
|
// user has turned off in the splash modal.
|
|
@@ -374,14 +467,20 @@ export async function decide(prompt, opts) {
|
|
|
374
467
|
isProviderDisabled(tierModel.provider)) {
|
|
375
468
|
tierModel = undefined;
|
|
376
469
|
}
|
|
470
|
+
const hotModel = tierModel?.id ?? opts.defaultModel;
|
|
471
|
+
const peak = adjustPeakHourModel(hotModel);
|
|
377
472
|
const d = {
|
|
378
473
|
tier: "hot",
|
|
379
|
-
model:
|
|
380
|
-
provider: tierModel?.provider ??
|
|
381
|
-
reason:
|
|
474
|
+
model: peak.modelId,
|
|
475
|
+
provider: tierModel?.provider ?? peak.provider,
|
|
476
|
+
reason: peak.adjusted
|
|
477
|
+
? `${effective ? `${c.reason}-rerouted(disabled-default)` : c.reason}|${peak.reason}`
|
|
478
|
+
: effective
|
|
479
|
+
? `${c.reason}-rerouted(disabled-default)`
|
|
480
|
+
: c.reason,
|
|
382
481
|
confidence: c.confidence,
|
|
383
482
|
};
|
|
384
|
-
const checked = await capCheck(d, opts
|
|
483
|
+
const checked = await capCheck(d, opts);
|
|
385
484
|
routerStore.setState({
|
|
386
485
|
tier: checked.tier,
|
|
387
486
|
lastDecision: checked,
|
|
@@ -395,7 +494,7 @@ export async function decide(prompt, opts) {
|
|
|
395
494
|
// Step 2: Warm path (EE /api/route-model, 250ms timeout)
|
|
396
495
|
const w = await callWarmRoute(prompt, { ...opts, context: routeCtx });
|
|
397
496
|
if (w) {
|
|
398
|
-
const checked = await capCheck(constrainToProvider(w, opts), opts
|
|
497
|
+
const checked = await capCheck(constrainToProvider(w, opts), opts);
|
|
399
498
|
routerStore.setState({
|
|
400
499
|
tier: checked.tier,
|
|
401
500
|
lastDecision: checked,
|
|
@@ -409,7 +508,7 @@ export async function decide(prompt, opts) {
|
|
|
409
508
|
// Step 3: Cold path (EE /api/cold-route, 1s timeout)
|
|
410
509
|
const cd = await callColdRoute(prompt, { ...opts, context: routeCtx });
|
|
411
510
|
if (cd) {
|
|
412
|
-
const checked = await capCheck(constrainToProvider(cd, opts), opts
|
|
511
|
+
const checked = await capCheck(constrainToProvider(cd, opts), opts);
|
|
413
512
|
routerStore.setState({
|
|
414
513
|
tier: "cold",
|
|
415
514
|
lastDecision: checked,
|
|
@@ -422,15 +521,18 @@ export async function decide(prompt, opts) {
|
|
|
422
521
|
}
|
|
423
522
|
// Step 4: Final fallback when EE entirely unreachable
|
|
424
523
|
const effective = resolveEffectiveDefaults(opts);
|
|
524
|
+
const peak = adjustPeakHourModel(effective.model);
|
|
425
525
|
const fallback = {
|
|
426
526
|
tier: routerStore.getState().degraded ? "degraded" : "hot",
|
|
427
|
-
model:
|
|
428
|
-
provider:
|
|
429
|
-
reason:
|
|
430
|
-
? "fallback:ee-unreachable+rerouted(disabled-default)"
|
|
431
|
-
:
|
|
527
|
+
model: peak.modelId,
|
|
528
|
+
provider: peak.provider,
|
|
529
|
+
reason: peak.adjusted
|
|
530
|
+
? `${effective.provider !== opts.defaultProvider ? "fallback:ee-unreachable+rerouted(disabled-default)" : "fallback:ee-unreachable"}|${peak.reason}`
|
|
531
|
+
: effective.provider !== opts.defaultProvider
|
|
532
|
+
? "fallback:ee-unreachable+rerouted(disabled-default)"
|
|
533
|
+
: "fallback:ee-unreachable",
|
|
432
534
|
};
|
|
433
|
-
const checked = await capCheck(fallback, opts
|
|
535
|
+
const checked = await capCheck(fallback, opts);
|
|
434
536
|
routerStore.setState({
|
|
435
537
|
lastDecision: checked,
|
|
436
538
|
taskHash: null,
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Peak-hour routing — rules loaded from catalog API `provider_policies.peak_hour`
|
|
3
|
+
* (vendor-sourced metadata). User settings only toggle enabled + switch/downgrade mode.
|
|
4
|
+
*/
|
|
5
|
+
import type { CatalogProviderPeakHour } from "../models/catalog-client.js";
|
|
6
|
+
import type { ProviderId } from "../providers/types.js";
|
|
7
|
+
import type { ModelInfo } from "../types/index.js";
|
|
8
|
+
import { type PeakHourPolicy } from "../utils/settings.js";
|
|
9
|
+
export type PeakHourMode = "downgrade" | "switch";
|
|
10
|
+
export interface PeakHourAdjustment {
|
|
11
|
+
modelId: string;
|
|
12
|
+
provider: ProviderId;
|
|
13
|
+
adjusted: boolean;
|
|
14
|
+
reason?: string;
|
|
15
|
+
}
|
|
16
|
+
export declare function hourInTimezone(now: Date, timezone: string): number;
|
|
17
|
+
/** @deprecated Use hourInTimezone — kept for tests referencing UTC+8. */
|
|
18
|
+
export declare function hourUtc8(now: Date): number;
|
|
19
|
+
export declare function peakHourWindows(rule: CatalogProviderPeakHour): Array<{
|
|
20
|
+
start_hour: number;
|
|
21
|
+
end_hour: number;
|
|
22
|
+
}>;
|
|
23
|
+
export declare function isPeakHourForProvider(provider: ProviderId, now: Date, userPolicy?: PeakHourPolicy): boolean;
|
|
24
|
+
/** @deprecated Use isPeakHourForProvider(provider, ...) — window is per-provider from catalog. */
|
|
25
|
+
export declare function isPeakHourUtc8(now: Date, userPolicy: PeakHourPolicy): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Adjust a concrete model id for peak-hour policy. No-op outside the provider's
|
|
28
|
+
* catalog-defined window or when user policy is disabled.
|
|
29
|
+
*/
|
|
30
|
+
export declare function adjustPeakHourModel(modelId: string, opts?: {
|
|
31
|
+
now?: Date;
|
|
32
|
+
policy?: PeakHourPolicy;
|
|
33
|
+
}): PeakHourAdjustment;
|
|
34
|
+
/** Tier lookup with peak-hour adjustment applied to the resolved model. */
|
|
35
|
+
export declare function getRoutedModelByTier(tier: "fast" | "balanced" | "premium", preferProvider?: string, opts?: {
|
|
36
|
+
now?: Date;
|
|
37
|
+
policy?: PeakHourPolicy;
|
|
38
|
+
}): ModelInfo | undefined;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { getModelByTier, getModelInfo, getProviderPeakHourRule, SWITCH_PROVIDER_ORDER } from "../models/registry.js";
|
|
2
|
+
import { detectProviderForModel } from "../providers/runtime.js";
|
|
3
|
+
import { getPeakHourPolicy, isProviderDisabled } from "../utils/settings.js";
|
|
4
|
+
export function hourInTimezone(now, timezone) {
|
|
5
|
+
const parts = new Intl.DateTimeFormat("en-US", {
|
|
6
|
+
timeZone: timezone,
|
|
7
|
+
hour: "numeric",
|
|
8
|
+
hour12: false,
|
|
9
|
+
}).formatToParts(now);
|
|
10
|
+
const hour = parts.find((p) => p.type === "hour")?.value;
|
|
11
|
+
return hour ? Number(hour) : 0;
|
|
12
|
+
}
|
|
13
|
+
/** @deprecated Use hourInTimezone — kept for tests referencing UTC+8. */
|
|
14
|
+
export function hourUtc8(now) {
|
|
15
|
+
return hourInTimezone(now, "Asia/Shanghai");
|
|
16
|
+
}
|
|
17
|
+
export function peakHourWindows(rule) {
|
|
18
|
+
if (rule.windows?.length)
|
|
19
|
+
return rule.windows;
|
|
20
|
+
if (rule.start_hour != null && rule.end_hour != null) {
|
|
21
|
+
return [{ start_hour: rule.start_hour, end_hour: rule.end_hour }];
|
|
22
|
+
}
|
|
23
|
+
return [];
|
|
24
|
+
}
|
|
25
|
+
export function isPeakHourForProvider(provider, now, userPolicy = getPeakHourPolicy()) {
|
|
26
|
+
if (userPolicy.enabled === false)
|
|
27
|
+
return false;
|
|
28
|
+
const rule = getProviderPeakHourRule(provider);
|
|
29
|
+
if (!rule)
|
|
30
|
+
return false;
|
|
31
|
+
const h = hourInTimezone(now, rule.timezone);
|
|
32
|
+
return peakHourWindows(rule).some((w) => h >= w.start_hour && h < w.end_hour);
|
|
33
|
+
}
|
|
34
|
+
/** @deprecated Use isPeakHourForProvider(provider, ...) — window is per-provider from catalog. */
|
|
35
|
+
export function isPeakHourUtc8(now, userPolicy) {
|
|
36
|
+
return isPeakHourForProvider("zai", now, userPolicy) || isPeakHourForProvider("deepseek", now, userPolicy);
|
|
37
|
+
}
|
|
38
|
+
function sameProviderDowngrade(modelId, provider) {
|
|
39
|
+
const rule = getProviderPeakHourRule(provider);
|
|
40
|
+
if (!rule)
|
|
41
|
+
return null;
|
|
42
|
+
if (!rule.sensitive_model_ids.includes(modelId))
|
|
43
|
+
return null;
|
|
44
|
+
return rule.fallback_model_id;
|
|
45
|
+
}
|
|
46
|
+
function pickSwitchFallback(excludeProvider) {
|
|
47
|
+
const rule = getProviderPeakHourRule(excludeProvider);
|
|
48
|
+
const order = rule?.switch_fallback_providers ?? SWITCH_PROVIDER_ORDER;
|
|
49
|
+
for (const p of order) {
|
|
50
|
+
if (p === excludeProvider)
|
|
51
|
+
continue;
|
|
52
|
+
if (isProviderDisabled(p))
|
|
53
|
+
continue;
|
|
54
|
+
const m = getModelByTier("fast", p) ?? getModelByTier("balanced", p);
|
|
55
|
+
if (m && m.provider === p)
|
|
56
|
+
return m;
|
|
57
|
+
}
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Adjust a concrete model id for peak-hour policy. No-op outside the provider's
|
|
62
|
+
* catalog-defined window or when user policy is disabled.
|
|
63
|
+
*/
|
|
64
|
+
export function adjustPeakHourModel(modelId, opts) {
|
|
65
|
+
const userPolicy = opts?.policy ?? getPeakHourPolicy();
|
|
66
|
+
const now = opts?.now ?? new Date();
|
|
67
|
+
const provider = detectProviderForModel(modelId);
|
|
68
|
+
if (!isPeakHourForProvider(provider, now, userPolicy)) {
|
|
69
|
+
return { modelId, provider, adjusted: false };
|
|
70
|
+
}
|
|
71
|
+
const downgraded = sameProviderDowngrade(modelId, provider);
|
|
72
|
+
if (downgraded) {
|
|
73
|
+
if (userPolicy.mode === "switch") {
|
|
74
|
+
const alt = pickSwitchFallback(provider);
|
|
75
|
+
if (alt && alt.id !== modelId) {
|
|
76
|
+
return {
|
|
77
|
+
modelId: alt.id,
|
|
78
|
+
provider: alt.provider,
|
|
79
|
+
adjusted: true,
|
|
80
|
+
reason: `peak-hour(${provider}→${alt.provider}:${alt.id})`,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const info = getModelInfo(downgraded);
|
|
85
|
+
if (info?.provider === provider) {
|
|
86
|
+
return {
|
|
87
|
+
modelId: downgraded,
|
|
88
|
+
provider,
|
|
89
|
+
adjusted: true,
|
|
90
|
+
reason: `peak-hour(${modelId}→${downgraded})`,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return { modelId, provider, adjusted: false };
|
|
95
|
+
}
|
|
96
|
+
/** Tier lookup with peak-hour adjustment applied to the resolved model. */
|
|
97
|
+
export function getRoutedModelByTier(tier, preferProvider, opts) {
|
|
98
|
+
const base = getModelByTier(tier, preferProvider);
|
|
99
|
+
if (!base)
|
|
100
|
+
return undefined;
|
|
101
|
+
const adj = adjustPeakHourModel(base.id, opts);
|
|
102
|
+
if (!adj.adjusted)
|
|
103
|
+
return base;
|
|
104
|
+
const info = getModelInfo(adj.modelId);
|
|
105
|
+
return info ?? base;
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=peak-hour.js.map
|
|
@@ -28,6 +28,7 @@ import { getModelByTier } from "../models/registry.js";
|
|
|
28
28
|
import { detectProviderForModel } from "../providers/runtime.js";
|
|
29
29
|
import { readTimeoutEnv } from "../utils/ee-logger.js";
|
|
30
30
|
import { isModelDisabled, isProviderDisabled, loadUserSettings } from "../utils/settings.js";
|
|
31
|
+
import { getRoutedModelByTier } from "./peak-hour.js";
|
|
31
32
|
// ─── EE-guided SAMR override ─────────────────────────────────────────────────
|
|
32
33
|
// EE_SAMR_TIMEOUT: per-call budget for consulting the EE brain about SAMR.
|
|
33
34
|
// Default 2s, range 500ms..5s. Falls back to heuristics on timeout/error.
|
|
@@ -187,7 +188,7 @@ export function decideStepRouting(phase1ModelId, defaultProvider, config) {
|
|
|
187
188
|
// ─── Internal helpers ────────────────────────────────────────────────────────
|
|
188
189
|
function resolveExecutionModel(provider, tier, excludeModelId) {
|
|
189
190
|
// Try same provider first
|
|
190
|
-
const sameProvider =
|
|
191
|
+
const sameProvider = getRoutedModelByTier(tier, provider);
|
|
191
192
|
if (sameProvider && sameProvider.id !== excludeModelId) {
|
|
192
193
|
// Guard: check provider-disabled and model-disabled (mirrors cross-provider branch)
|
|
193
194
|
if (!isProviderDisabled(sameProvider.provider) && !isModelDisabled(sameProvider.id)) {
|
|
@@ -195,7 +196,7 @@ function resolveExecutionModel(provider, tier, excludeModelId) {
|
|
|
195
196
|
}
|
|
196
197
|
}
|
|
197
198
|
// If no same-provider model in this tier, try any provider
|
|
198
|
-
const anyProvider =
|
|
199
|
+
const anyProvider = getRoutedModelByTier(tier);
|
|
199
200
|
if (anyProvider && anyProvider.id !== excludeModelId) {
|
|
200
201
|
// Only use cross-provider if the provider and model aren't disabled
|
|
201
202
|
if (!isProviderDisabled(anyProvider.provider) && !isModelDisabled(anyProvider.id)) {
|
package/dist/src/router/warm.js
CHANGED
|
@@ -6,13 +6,13 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { routeModel as bridgeRouteModel } from "../ee/bridge.js";
|
|
8
8
|
import { getDefaultEEClient } from "../ee/intercept.js";
|
|
9
|
-
import {
|
|
9
|
+
import { getRoutedModelByTier } from "./peak-hour.js";
|
|
10
10
|
import { PROVIDER_INHERIT } from "./provider-sentinel.js";
|
|
11
11
|
/**
|
|
12
12
|
* Map the session's provider to the EE runtime whose tier ladder the EE serves.
|
|
13
13
|
* EE keys its `getModelTiers()` ladders by agent runtime, not provider, and only
|
|
14
|
-
* the catalog-backed runtimes (claude/codex
|
|
15
|
-
* Providers without an EE runtime (deepseek/
|
|
14
|
+
* the catalog-backed runtimes (claude/codex) resolve to a non-null model.
|
|
15
|
+
* Providers without an EE runtime (deepseek/xai) map to "" — EE then
|
|
16
16
|
* returns the routing TIER with a null model and we resolve the concrete model
|
|
17
17
|
* from the shared catalog for the session provider. This removes the old
|
|
18
18
|
* hardcoded `runtime:"claude"`, which leaked EE's stale `claude-*` ids into the
|
|
@@ -21,7 +21,6 @@ import { PROVIDER_INHERIT } from "./provider-sentinel.js";
|
|
|
21
21
|
const PROVIDER_TO_EE_RUNTIME = Object.freeze({
|
|
22
22
|
anthropic: "claude",
|
|
23
23
|
openai: "codex",
|
|
24
|
-
google: "gemini",
|
|
25
24
|
});
|
|
26
25
|
function eeRuntimeForProvider(provider) {
|
|
27
26
|
return (provider && PROVIDER_TO_EE_RUNTIME[provider]) || "";
|
|
@@ -36,7 +35,7 @@ export async function callWarmRoute(prompt, opts) {
|
|
|
36
35
|
// EE returns a model only for catalog-backed runtimes; otherwise resolve the
|
|
37
36
|
// concrete model from the catalog for the session provider + decided tier.
|
|
38
37
|
const catalogTier = TIER_TO_CATALOG_TIER[bridgeResult.tier] ?? "balanced";
|
|
39
|
-
const model = bridgeResult.model ||
|
|
38
|
+
const model = bridgeResult.model || getRoutedModelByTier(catalogTier, opts.defaultProvider)?.id;
|
|
40
39
|
if (model) {
|
|
41
40
|
return {
|
|
42
41
|
tier: bridgeResult.tier === "fast" ? "hot" : bridgeResult.tier === "premium" ? "cold" : "warm",
|
|
@@ -10,3 +10,14 @@ export interface ContinuationPromptInput {
|
|
|
10
10
|
installedPackages?: readonly string[];
|
|
11
11
|
}
|
|
12
12
|
export declare function buildIdealContinuationPrompt(input: ContinuationPromptInput): string;
|
|
13
|
+
export interface AdoptExistingPromptInput {
|
|
14
|
+
originalPrompt: string;
|
|
15
|
+
projectDir: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Build the continuation prompt fed to the LLM after the user adopts an EXISTING
|
|
19
|
+
* project via the CB-3 "Point to existing" recovery option. Unlike the init_new
|
|
20
|
+
* variant this makes no template/BB assumptions — the target is an arbitrary
|
|
21
|
+
* repo the user already has, with a verify recipe already detected there.
|
|
22
|
+
*/
|
|
23
|
+
export declare function buildAdoptExistingContinuationPrompt(input: AdoptExistingPromptInput): string;
|
|
@@ -63,4 +63,30 @@ Constraints:
|
|
|
63
63
|
- Don't ask "do you want me to continue?" — proceed end-to-end.
|
|
64
64
|
- If you hit a blocker that genuinely needs the user, state EXACTLY which decision is blocking and stop.`;
|
|
65
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* Build the continuation prompt fed to the LLM after the user adopts an EXISTING
|
|
68
|
+
* project via the CB-3 "Point to existing" recovery option. Unlike the init_new
|
|
69
|
+
* variant this makes no template/BB assumptions — the target is an arbitrary
|
|
70
|
+
* repo the user already has, with a verify recipe already detected there.
|
|
71
|
+
*/
|
|
72
|
+
export function buildAdoptExistingContinuationPrompt(input) {
|
|
73
|
+
const { originalPrompt, projectDir } = input;
|
|
74
|
+
return `You are continuing a /ideal product loop in an EXISTING project the user pointed to at ${projectDir}. A verify recipe was detected there, so the project already has a runnable test/build setup — adopt it, do NOT re-scaffold.
|
|
75
|
+
|
|
76
|
+
User's original request:
|
|
77
|
+
"${originalPrompt}"
|
|
78
|
+
|
|
79
|
+
Implement this now against the existing codebase. Do NOT ask the user to restate the request.
|
|
80
|
+
|
|
81
|
+
1. Discover structure (1-2 tool calls max): list the project directory, then read the manifest that exists (package.json / pyproject.toml / go.mod / Cargo.toml / *.csproj) and any README/AGENTS.md to learn the stack, entry point, and conventions. Do NOT assume files — skip whatever is absent.
|
|
82
|
+
2. Design briefly (5-10 lines): the entities/modules/endpoints to add, matching the conventions you observed (naming, error-handling, DI, folder layout).
|
|
83
|
+
3. Implement: create/edit files in the EXISTING layout; mirror the surrounding patterns. Never hardcode secrets or URLs — read config from the project's existing mechanism.
|
|
84
|
+
4. Verify: run the project's OWN detected test/build command; it must pass. Report the exact command + an output excerpt as evidence.
|
|
85
|
+
5. Summarize in 3-5 bullets: what was added, where, and how to run/test it.
|
|
86
|
+
|
|
87
|
+
Constraints:
|
|
88
|
+
- Your working directory is already ${projectDir}; do NOT cd into it and do NOT re-scaffold.
|
|
89
|
+
- Don't ask "do you want me to continue?" — proceed end-to-end.
|
|
90
|
+
- If a genuine blocker needs the user (missing API key, ambiguous requirement), state EXACTLY which decision is blocking and stop.`;
|
|
91
|
+
}
|
|
66
92
|
//# sourceMappingURL=continuation-prompt.js.map
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* Designed for testability: callers inject detectVerifyRecipe so unit tests
|
|
6
6
|
* never hit the real orchestrator.
|
|
7
7
|
*/
|
|
8
|
+
import type { VerifyRecipe } from "../types/index.js";
|
|
8
9
|
import { detectBBFramework } from "./init-new.js";
|
|
9
10
|
export interface PointToExistingOptions {
|
|
10
11
|
/** Raw path string entered by the user. May be relative or absolute. */
|
|
@@ -26,6 +27,26 @@ export interface PointToExistingResult {
|
|
|
26
27
|
targetFramework?: "muonroi-building-block" | string;
|
|
27
28
|
}
|
|
28
29
|
export { detectBBFramework };
|
|
30
|
+
/**
|
|
31
|
+
* Deterministic, filesystem-only verify-recipe detection for an existing project
|
|
32
|
+
* the user pointed to via the CB-3 recovery card.
|
|
33
|
+
*
|
|
34
|
+
* WHY NOT the orchestrator's detectVerifyRecipe: that path runs an LLM
|
|
35
|
+
* verify-detect sub-agent against the orchestrator's OWN (session) cwd via
|
|
36
|
+
* `this.bash.getCwd()` — it cannot inspect an arbitrary pointed-to directory, so
|
|
37
|
+
* wiring it here was deferred and the handler stubbed to `null` (point-to-existing
|
|
38
|
+
* therefore ALWAYS reported "no recipe", making the recovery option inert).
|
|
39
|
+
* `inferVerifyProjectProfile` reads the target dir's manifest/build files
|
|
40
|
+
* directly (package.json test/check/lint, Makefile, pyproject, go.mod, cargo,
|
|
41
|
+
* maven/gradle, *.csproj) — the correct detector for adopting an existing repo.
|
|
42
|
+
*
|
|
43
|
+
* Returns the recipe only when it is actually RUNNABLE (has a test/build/start
|
|
44
|
+
* command). The bare "unknown" fallback that `inferVerifyProjectProfile` emits
|
|
45
|
+
* for a directory with no recognizable project files carries empty commands →
|
|
46
|
+
* we return null so point-to-existing correctly reports `no_recipe` instead of
|
|
47
|
+
* falsely adopting an empty directory.
|
|
48
|
+
*/
|
|
49
|
+
export declare function detectExistingProjectRecipe(cwd: string): VerifyRecipe | null;
|
|
29
50
|
/**
|
|
30
51
|
* Resolve and validate a user-provided path for sprint re-entry.
|
|
31
52
|
*
|
|
@@ -7,8 +7,33 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { realpathSync, statSync } from "node:fs";
|
|
9
9
|
import * as path from "node:path";
|
|
10
|
+
import { inferVerifyProjectProfile } from "../verify/recipes.js";
|
|
10
11
|
import { detectBBFramework } from "./init-new.js";
|
|
11
12
|
export { detectBBFramework };
|
|
13
|
+
/**
|
|
14
|
+
* Deterministic, filesystem-only verify-recipe detection for an existing project
|
|
15
|
+
* the user pointed to via the CB-3 recovery card.
|
|
16
|
+
*
|
|
17
|
+
* WHY NOT the orchestrator's detectVerifyRecipe: that path runs an LLM
|
|
18
|
+
* verify-detect sub-agent against the orchestrator's OWN (session) cwd via
|
|
19
|
+
* `this.bash.getCwd()` — it cannot inspect an arbitrary pointed-to directory, so
|
|
20
|
+
* wiring it here was deferred and the handler stubbed to `null` (point-to-existing
|
|
21
|
+
* therefore ALWAYS reported "no recipe", making the recovery option inert).
|
|
22
|
+
* `inferVerifyProjectProfile` reads the target dir's manifest/build files
|
|
23
|
+
* directly (package.json test/check/lint, Makefile, pyproject, go.mod, cargo,
|
|
24
|
+
* maven/gradle, *.csproj) — the correct detector for adopting an existing repo.
|
|
25
|
+
*
|
|
26
|
+
* Returns the recipe only when it is actually RUNNABLE (has a test/build/start
|
|
27
|
+
* command). The bare "unknown" fallback that `inferVerifyProjectProfile` emits
|
|
28
|
+
* for a directory with no recognizable project files carries empty commands →
|
|
29
|
+
* we return null so point-to-existing correctly reports `no_recipe` instead of
|
|
30
|
+
* falsely adopting an empty directory.
|
|
31
|
+
*/
|
|
32
|
+
export function detectExistingProjectRecipe(cwd) {
|
|
33
|
+
const { recipe } = inferVerifyProjectProfile(cwd);
|
|
34
|
+
const runnable = recipe.testCommands.length > 0 || recipe.buildCommands.length > 0 || Boolean(recipe.startCommand);
|
|
35
|
+
return runnable ? recipe : null;
|
|
36
|
+
}
|
|
12
37
|
// ---------------------------------------------------------------------------
|
|
13
38
|
// Implementation
|
|
14
39
|
// ---------------------------------------------------------------------------
|