agent-nuvira 3.3.13 → 3.3.15
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 +67 -1
- package/dist/agents/agents/planner.d.ts.map +1 -1
- package/dist/agents/agents/planner.js +9 -0
- package/dist/agents/agents/planner.js.map +1 -1
- package/dist/agents/orchestrator.d.ts +31 -0
- package/dist/agents/orchestrator.d.ts.map +1 -1
- package/dist/agents/orchestrator.js +74 -126
- package/dist/agents/orchestrator.js.map +1 -1
- package/dist/cli/chat.d.ts +89 -0
- package/dist/cli/chat.d.ts.map +1 -1
- package/dist/cli/chat.js +765 -33
- package/dist/cli/chat.js.map +1 -1
- package/dist/cli/cli-program.d.ts.map +1 -1
- package/dist/cli/cli-program.js +12 -0
- package/dist/cli/cli-program.js.map +1 -1
- package/dist/cli/config.d.ts +12 -0
- package/dist/cli/config.d.ts.map +1 -1
- package/dist/cli/config.js +86 -0
- package/dist/cli/config.js.map +1 -1
- package/dist/cli/decisions.d.ts +28 -0
- package/dist/cli/decisions.d.ts.map +1 -0
- package/dist/cli/decisions.js +140 -0
- package/dist/cli/decisions.js.map +1 -0
- package/dist/cli/doctor.d.ts +6 -0
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +46 -0
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/failover-runner.d.ts.map +1 -1
- package/dist/cli/failover-runner.js +19 -1
- package/dist/cli/failover-runner.js.map +1 -1
- package/dist/cli/feedback.d.ts.map +1 -1
- package/dist/cli/feedback.js +5 -1
- package/dist/cli/feedback.js.map +1 -1
- package/dist/cli/hooks.d.ts +30 -0
- package/dist/cli/hooks.d.ts.map +1 -0
- package/dist/cli/hooks.js +224 -0
- package/dist/cli/hooks.js.map +1 -0
- package/dist/cli/knowledge.d.ts +9 -1
- package/dist/cli/knowledge.d.ts.map +1 -1
- package/dist/cli/knowledge.js +93 -2
- package/dist/cli/knowledge.js.map +1 -1
- package/dist/cli/loop-executor.d.ts.map +1 -1
- package/dist/cli/loop-executor.js +47 -12
- package/dist/cli/loop-executor.js.map +1 -1
- package/dist/cli/model.d.ts +20 -0
- package/dist/cli/model.d.ts.map +1 -1
- package/dist/cli/model.js +294 -14
- package/dist/cli/model.js.map +1 -1
- package/dist/cli/rate.d.ts +78 -0
- package/dist/cli/rate.d.ts.map +1 -0
- package/dist/cli/rate.js +294 -0
- package/dist/cli/rate.js.map +1 -0
- package/dist/config/process-env.d.ts.map +1 -1
- package/dist/config/process-env.js +10 -0
- package/dist/config/process-env.js.map +1 -1
- package/dist/enterprise/vault.d.ts.map +1 -1
- package/dist/enterprise/vault.js +5 -21
- package/dist/enterprise/vault.js.map +1 -1
- package/dist/gateway/hook-contract.d.ts +33 -0
- package/dist/gateway/hook-contract.d.ts.map +1 -1
- package/dist/gateway/hook-contract.js +97 -0
- package/dist/gateway/hook-contract.js.map +1 -1
- package/dist/inference/gemini-adapter.d.ts.map +1 -1
- package/dist/inference/gemini-adapter.js +4 -0
- package/dist/inference/gemini-adapter.js.map +1 -1
- package/dist/inference/interface.d.ts +37 -0
- package/dist/inference/interface.d.ts.map +1 -1
- package/dist/inference/native-tools.d.ts +16 -1
- package/dist/inference/native-tools.d.ts.map +1 -1
- package/dist/inference/native-tools.js +34 -10
- package/dist/inference/native-tools.js.map +1 -1
- package/dist/inference/route-resolver.d.ts +30 -0
- package/dist/inference/route-resolver.d.ts.map +1 -1
- package/dist/inference/route-resolver.js +158 -1
- package/dist/inference/route-resolver.js.map +1 -1
- package/dist/inference/tools.d.ts +16 -1
- package/dist/inference/tools.d.ts.map +1 -1
- package/dist/inference/tools.js +51 -18
- package/dist/inference/tools.js.map +1 -1
- package/dist/learning/acceptance-model.d.ts +247 -0
- package/dist/learning/acceptance-model.d.ts.map +1 -0
- package/dist/learning/acceptance-model.js +708 -0
- package/dist/learning/acceptance-model.js.map +1 -0
- package/dist/learning/auto-router.d.ts +68 -24
- package/dist/learning/auto-router.d.ts.map +1 -1
- package/dist/learning/auto-router.js +301 -96
- package/dist/learning/auto-router.js.map +1 -1
- package/dist/learning/autonomy-policy.d.ts +12 -16
- package/dist/learning/autonomy-policy.d.ts.map +1 -1
- package/dist/learning/autonomy-policy.js +50 -48
- package/dist/learning/autonomy-policy.js.map +1 -1
- package/dist/learning/behavioural-labels.d.ts +86 -0
- package/dist/learning/behavioural-labels.d.ts.map +1 -0
- package/dist/learning/behavioural-labels.js +166 -0
- package/dist/learning/behavioural-labels.js.map +1 -0
- package/dist/learning/capability-evidence.d.ts +167 -0
- package/dist/learning/capability-evidence.d.ts.map +1 -0
- package/dist/learning/capability-evidence.js +210 -0
- package/dist/learning/capability-evidence.js.map +1 -0
- package/dist/learning/capability-readiness.d.ts +64 -0
- package/dist/learning/capability-readiness.d.ts.map +1 -0
- package/dist/learning/capability-readiness.js +74 -0
- package/dist/learning/capability-readiness.js.map +1 -0
- package/dist/learning/capability-types.d.ts +169 -0
- package/dist/learning/capability-types.d.ts.map +1 -0
- package/dist/learning/capability-types.js +20 -0
- package/dist/learning/capability-types.js.map +1 -0
- package/dist/learning/catalog-feed.d.ts +89 -0
- package/dist/learning/catalog-feed.d.ts.map +1 -0
- package/dist/learning/catalog-feed.js +211 -0
- package/dist/learning/catalog-feed.js.map +1 -0
- package/dist/learning/cli-intent-effects.d.ts +60 -0
- package/dist/learning/cli-intent-effects.d.ts.map +1 -0
- package/dist/learning/cli-intent-effects.js +153 -0
- package/dist/learning/cli-intent-effects.js.map +1 -0
- package/dist/learning/command-adaptation.d.ts +41 -0
- package/dist/learning/command-adaptation.d.ts.map +1 -0
- package/dist/learning/command-adaptation.js +122 -0
- package/dist/learning/command-adaptation.js.map +1 -0
- package/dist/learning/consent-picture.d.ts +42 -0
- package/dist/learning/consent-picture.d.ts.map +1 -0
- package/dist/learning/consent-picture.js +53 -0
- package/dist/learning/consent-picture.js.map +1 -0
- package/dist/learning/context-budget.d.ts +37 -6
- package/dist/learning/context-budget.d.ts.map +1 -1
- package/dist/learning/context-budget.js +38 -9
- package/dist/learning/context-budget.js.map +1 -1
- package/dist/learning/decision-log.d.ts +114 -0
- package/dist/learning/decision-log.d.ts.map +1 -0
- package/dist/learning/decision-log.js +296 -0
- package/dist/learning/decision-log.js.map +1 -0
- package/dist/learning/deliverable-class.d.ts.map +1 -1
- package/dist/learning/deliverable-class.js +19 -0
- package/dist/learning/deliverable-class.js.map +1 -1
- package/dist/learning/deliverable-corpus.d.ts +117 -0
- package/dist/learning/deliverable-corpus.d.ts.map +1 -0
- package/dist/learning/deliverable-corpus.js +176 -0
- package/dist/learning/deliverable-corpus.js.map +1 -0
- package/dist/learning/error-repair.d.ts +22 -1
- package/dist/learning/error-repair.d.ts.map +1 -1
- package/dist/learning/error-repair.js +37 -0
- package/dist/learning/error-repair.js.map +1 -1
- package/dist/learning/exhaustion-report.d.ts +136 -0
- package/dist/learning/exhaustion-report.d.ts.map +1 -0
- package/dist/learning/exhaustion-report.js +239 -0
- package/dist/learning/exhaustion-report.js.map +1 -0
- package/dist/learning/failure-bookkeeping.d.ts +16 -0
- package/dist/learning/failure-bookkeeping.d.ts.map +1 -1
- package/dist/learning/failure-bookkeeping.js +51 -5
- package/dist/learning/failure-bookkeeping.js.map +1 -1
- package/dist/learning/gate-friction.d.ts +39 -0
- package/dist/learning/gate-friction.d.ts.map +1 -0
- package/dist/learning/gate-friction.js +91 -0
- package/dist/learning/gate-friction.js.map +1 -0
- package/dist/learning/harness-narration.d.ts +34 -0
- package/dist/learning/harness-narration.d.ts.map +1 -0
- package/dist/learning/harness-narration.js +58 -0
- package/dist/learning/harness-narration.js.map +1 -0
- package/dist/learning/hub-skill-catalog.d.ts.map +1 -1
- package/dist/learning/hub-skill-catalog.js +1 -2
- package/dist/learning/hub-skill-catalog.js.map +1 -1
- package/dist/learning/hybrid-router.d.ts +65 -0
- package/dist/learning/hybrid-router.d.ts.map +1 -1
- package/dist/learning/hybrid-router.js +186 -0
- package/dist/learning/hybrid-router.js.map +1 -1
- package/dist/learning/knowledge-base.d.ts +229 -3
- package/dist/learning/knowledge-base.d.ts.map +1 -1
- package/dist/learning/knowledge-base.js +603 -25
- package/dist/learning/knowledge-base.js.map +1 -1
- package/dist/learning/knowledge-eval.d.ts +171 -0
- package/dist/learning/knowledge-eval.d.ts.map +1 -0
- package/dist/learning/knowledge-eval.js +196 -0
- package/dist/learning/knowledge-eval.js.map +1 -0
- package/dist/learning/knowledge-turn.d.ts +92 -0
- package/dist/learning/knowledge-turn.d.ts.map +1 -0
- package/dist/learning/knowledge-turn.js +190 -0
- package/dist/learning/knowledge-turn.js.map +1 -0
- package/dist/learning/learned-commands.d.ts +64 -0
- package/dist/learning/learned-commands.d.ts.map +1 -0
- package/dist/learning/learned-commands.js +108 -0
- package/dist/learning/learned-commands.js.map +1 -0
- package/dist/learning/lexical-search.d.ts +50 -0
- package/dist/learning/lexical-search.d.ts.map +1 -0
- package/dist/learning/lexical-search.js +111 -0
- package/dist/learning/lexical-search.js.map +1 -0
- package/dist/learning/machine-facts.d.ts +71 -0
- package/dist/learning/machine-facts.d.ts.map +1 -0
- package/dist/learning/machine-facts.js +164 -0
- package/dist/learning/machine-facts.js.map +1 -0
- package/dist/learning/markdown-outline.d.ts +73 -0
- package/dist/learning/markdown-outline.d.ts.map +1 -0
- package/dist/learning/markdown-outline.js +136 -0
- package/dist/learning/markdown-outline.js.map +1 -0
- package/dist/learning/model-first-router.d.ts +33 -1
- package/dist/learning/model-first-router.d.ts.map +1 -1
- package/dist/learning/model-first-router.js +123 -9
- package/dist/learning/model-first-router.js.map +1 -1
- package/dist/learning/model-identity.d.ts +80 -0
- package/dist/learning/model-identity.d.ts.map +1 -0
- package/dist/learning/model-identity.js +115 -0
- package/dist/learning/model-identity.js.map +1 -0
- package/dist/learning/model-registry.d.ts +141 -2
- package/dist/learning/model-registry.d.ts.map +1 -1
- package/dist/learning/model-registry.js +324 -38
- package/dist/learning/model-registry.js.map +1 -1
- package/dist/learning/model-scoring.d.ts.map +1 -1
- package/dist/learning/model-scoring.js +8 -1
- package/dist/learning/model-scoring.js.map +1 -1
- package/dist/learning/model-selection.d.ts.map +1 -1
- package/dist/learning/model-selection.js +41 -0
- package/dist/learning/model-selection.js.map +1 -1
- package/dist/learning/outcome-observation.d.ts +49 -0
- package/dist/learning/outcome-observation.d.ts.map +1 -0
- package/dist/learning/outcome-observation.js +95 -0
- package/dist/learning/outcome-observation.js.map +1 -0
- package/dist/learning/pair-entitlement.d.ts +184 -0
- package/dist/learning/pair-entitlement.d.ts.map +1 -0
- package/dist/learning/pair-entitlement.js +214 -0
- package/dist/learning/pair-entitlement.js.map +1 -0
- package/dist/learning/provider-fallback.d.ts +23 -1
- package/dist/learning/provider-fallback.d.ts.map +1 -1
- package/dist/learning/provider-fallback.js +24 -0
- package/dist/learning/provider-fallback.js.map +1 -1
- package/dist/learning/reasoning-trace.d.ts +65 -1
- package/dist/learning/reasoning-trace.d.ts.map +1 -1
- package/dist/learning/reasoning-trace.js +123 -1
- package/dist/learning/reasoning-trace.js.map +1 -1
- package/dist/learning/requirement-probe.d.ts +55 -0
- package/dist/learning/requirement-probe.d.ts.map +1 -0
- package/dist/learning/requirement-probe.js +77 -0
- package/dist/learning/requirement-probe.js.map +1 -0
- package/dist/learning/resilient-call.d.ts +20 -1
- package/dist/learning/resilient-call.d.ts.map +1 -1
- package/dist/learning/resilient-call.js +42 -4
- package/dist/learning/resilient-call.js.map +1 -1
- package/dist/learning/response-usability.d.ts +85 -0
- package/dist/learning/response-usability.d.ts.map +1 -0
- package/dist/learning/response-usability.js +135 -0
- package/dist/learning/response-usability.js.map +1 -0
- package/dist/learning/router-bandit.d.ts +132 -0
- package/dist/learning/router-bandit.d.ts.map +1 -1
- package/dist/learning/router-bandit.js +253 -0
- package/dist/learning/router-bandit.js.map +1 -1
- package/dist/learning/routing-history.d.ts +41 -2
- package/dist/learning/routing-history.d.ts.map +1 -1
- package/dist/learning/routing-history.js.map +1 -1
- package/dist/learning/run-trace.d.ts +21 -0
- package/dist/learning/run-trace.d.ts.map +1 -1
- package/dist/learning/run-trace.js +38 -0
- package/dist/learning/run-trace.js.map +1 -1
- package/dist/learning/session-grant.d.ts +75 -0
- package/dist/learning/session-grant.d.ts.map +1 -0
- package/dist/learning/session-grant.js +112 -0
- package/dist/learning/session-grant.js.map +1 -0
- package/dist/learning/task-model-continuity.d.ts +73 -0
- package/dist/learning/task-model-continuity.d.ts.map +1 -0
- package/dist/learning/task-model-continuity.js +87 -0
- package/dist/learning/task-model-continuity.js.map +1 -0
- package/dist/learning/turn-feedback.d.ts +93 -0
- package/dist/learning/turn-feedback.d.ts.map +1 -0
- package/dist/learning/turn-feedback.js +127 -0
- package/dist/learning/turn-feedback.js.map +1 -0
- package/dist/learning/turn-report.d.ts +104 -2
- package/dist/learning/turn-report.d.ts.map +1 -1
- package/dist/learning/turn-report.js +161 -6
- package/dist/learning/turn-report.js.map +1 -1
- package/dist/learning/verdict-routing.d.ts +51 -0
- package/dist/learning/verdict-routing.d.ts.map +1 -0
- package/dist/learning/verdict-routing.js +122 -0
- package/dist/learning/verdict-routing.js.map +1 -0
- package/dist/mcp/client.d.ts.map +1 -1
- package/dist/mcp/client.js +5 -0
- package/dist/mcp/client.js.map +1 -1
- package/dist/mcp/manager.d.ts +10 -0
- package/dist/mcp/manager.d.ts.map +1 -1
- package/dist/mcp/manager.js +20 -0
- package/dist/mcp/manager.js.map +1 -1
- package/dist/mcp/mcp-discovery.d.ts +34 -0
- package/dist/mcp/mcp-discovery.d.ts.map +1 -0
- package/dist/mcp/mcp-discovery.js +71 -0
- package/dist/mcp/mcp-discovery.js.map +1 -0
- package/dist/mcp/mcp-schema-cache.d.ts +17 -0
- package/dist/mcp/mcp-schema-cache.d.ts.map +1 -1
- package/dist/mcp/mcp-schema-cache.js +47 -3
- package/dist/mcp/mcp-schema-cache.js.map +1 -1
- package/dist/mcp/types.d.ts +28 -0
- package/dist/mcp/types.d.ts.map +1 -1
- package/dist/mcp/types.js.map +1 -1
- package/dist/parity/drivers.d.ts.map +1 -1
- package/dist/parity/drivers.js +3 -0
- package/dist/parity/drivers.js.map +1 -1
- package/dist/parity/observation.d.ts +12 -0
- package/dist/parity/observation.d.ts.map +1 -1
- package/dist/parity/observation.js +14 -0
- package/dist/parity/observation.js.map +1 -1
- package/dist/tools/ask-user.d.ts.map +1 -1
- package/dist/tools/ask-user.js +4 -0
- package/dist/tools/ask-user.js.map +1 -1
- package/dist/tools/capability-registry.d.ts +150 -0
- package/dist/tools/capability-registry.d.ts.map +1 -0
- package/dist/tools/capability-registry.js +400 -0
- package/dist/tools/capability-registry.js.map +1 -0
- package/dist/tools/child-agent-runtime.d.ts.map +1 -1
- package/dist/tools/child-agent-runtime.js +19 -5
- package/dist/tools/child-agent-runtime.js.map +1 -1
- package/dist/tools/coding-tools.d.ts +17 -5
- package/dist/tools/coding-tools.d.ts.map +1 -1
- package/dist/tools/coding-tools.js +80 -11
- package/dist/tools/coding-tools.js.map +1 -1
- package/dist/tools/command-memo.d.ts +116 -0
- package/dist/tools/command-memo.d.ts.map +1 -0
- package/dist/tools/command-memo.js +202 -0
- package/dist/tools/command-memo.js.map +1 -0
- package/dist/tools/git-tool.d.ts.map +1 -1
- package/dist/tools/git-tool.js +13 -3
- package/dist/tools/git-tool.js.map +1 -1
- package/dist/tools/harness-directive.d.ts +42 -0
- package/dist/tools/harness-directive.d.ts.map +1 -0
- package/dist/tools/harness-directive.js +45 -0
- package/dist/tools/harness-directive.js.map +1 -0
- package/dist/tools/loop-skill-hint.d.ts +74 -174
- package/dist/tools/loop-skill-hint.d.ts.map +1 -1
- package/dist/tools/loop-skill-hint.js +110 -536
- package/dist/tools/loop-skill-hint.js.map +1 -1
- package/dist/tools/mcp-client-tool.d.ts +2 -0
- package/dist/tools/mcp-client-tool.d.ts.map +1 -1
- package/dist/tools/mcp-client-tool.js +3 -0
- package/dist/tools/mcp-client-tool.js.map +1 -1
- package/dist/tools/modality/shared.d.ts +1 -4
- package/dist/tools/modality/shared.d.ts.map +1 -1
- package/dist/tools/modality/shared.js +7 -22
- package/dist/tools/modality/shared.js.map +1 -1
- package/dist/tools/plan-store.d.ts +20 -2
- package/dist/tools/plan-store.d.ts.map +1 -1
- package/dist/tools/plan-store.js +51 -7
- package/dist/tools/plan-store.js.map +1 -1
- package/dist/tools/registry.d.ts +76 -3
- package/dist/tools/registry.d.ts.map +1 -1
- package/dist/tools/registry.js +486 -26
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/remediation-ladder.d.ts.map +1 -1
- package/dist/tools/remediation-ladder.js +8 -0
- package/dist/tools/remediation-ladder.js.map +1 -1
- package/dist/tools/run-cli.d.ts.map +1 -1
- package/dist/tools/run-cli.js +29 -4
- package/dist/tools/run-cli.js.map +1 -1
- package/dist/tools/run-terminal.d.ts +11 -0
- package/dist/tools/run-terminal.d.ts.map +1 -1
- package/dist/tools/run-terminal.js +265 -10
- package/dist/tools/run-terminal.js.map +1 -1
- package/dist/tools/tool-loop.d.ts +321 -13
- package/dist/tools/tool-loop.d.ts.map +1 -1
- package/dist/tools/tool-loop.js +867 -66
- package/dist/tools/tool-loop.js.map +1 -1
- package/dist/utils/atomic-store.d.ts +53 -0
- package/dist/utils/atomic-store.d.ts.map +1 -0
- package/dist/utils/atomic-store.js +178 -0
- package/dist/utils/atomic-store.js.map +1 -0
- package/dist/utils/binary-probe.d.ts +25 -0
- package/dist/utils/binary-probe.d.ts.map +1 -0
- package/dist/utils/binary-probe.js +48 -0
- package/dist/utils/binary-probe.js.map +1 -0
- package/dist/utils/workspace-path.d.ts +50 -0
- package/dist/utils/workspace-path.d.ts.map +1 -0
- package/dist/utils/workspace-path.js +86 -0
- package/dist/utils/workspace-path.js.map +1 -0
- package/dist/web-dashboard/chat-console.d.ts +24 -0
- package/dist/web-dashboard/chat-console.d.ts.map +1 -1
- package/dist/web-dashboard/chat-console.js +38 -0
- package/dist/web-dashboard/chat-console.js.map +1 -1
- package/dist/web-dashboard/project-context.d.ts +27 -2
- package/dist/web-dashboard/project-context.d.ts.map +1 -1
- package/dist/web-dashboard/project-context.js +32 -2
- package/dist/web-dashboard/project-context.js.map +1 -1
- package/dist/web-dashboard/server.d.ts +11 -0
- package/dist/web-dashboard/server.d.ts.map +1 -1
- package/dist/web-dashboard/server.js +362 -11
- package/dist/web-dashboard/server.js.map +1 -1
- package/dist/web-dashboard/src/types.d.ts +170 -1
- package/dist/web-dashboard/src/types.d.ts.map +1 -1
- package/dist/web-dashboard/workspace-guard.d.ts +11 -2
- package/dist/web-dashboard/workspace-guard.d.ts.map +1 -1
- package/dist/web-dashboard/workspace-guard.js +24 -12
- package/dist/web-dashboard/workspace-guard.js.map +1 -1
- package/dist/web-dashboard/workspace-resolution.d.ts +87 -0
- package/dist/web-dashboard/workspace-resolution.d.ts.map +1 -0
- package/dist/web-dashboard/workspace-resolution.js +105 -0
- package/dist/web-dashboard/workspace-resolution.js.map +1 -0
- package/package.json +1 -1
- package/src/web-dashboard/public/assets/index-zjbdJnO-.js +210 -0
- package/src/web-dashboard/public/assets/index-zjbdJnO-.js.map +1 -0
- package/src/web-dashboard/public/index.html +1 -1
- package/dist/agents/nvda-addon.d.ts +0 -14
- package/dist/agents/nvda-addon.js +0 -52
- package/dist/forwarded.d.ts +0 -2
- package/dist/forwarded.js +0 -2
- package/src/web-dashboard/public/assets/index-DhiuR4S2.js +0 -210
- package/src/web-dashboard/public/assets/index-DhiuR4S2.js.map +0 -1
|
@@ -40,12 +40,14 @@ import { analyzeComplexity } from './hybrid-router.js';
|
|
|
40
40
|
import { getTaskType } from './model-router.js';
|
|
41
41
|
import { getBenchmarkRuns } from './benchmark.js';
|
|
42
42
|
import { getAgentStats } from './agent-stats.js';
|
|
43
|
-
import { getRouterBandit, DEFAULT_MIN_SAMPLES } from './router-bandit.js';
|
|
43
|
+
import { getRouterBandit, DEFAULT_MIN_SAMPLES, } from './router-bandit.js';
|
|
44
44
|
import { getRouterPromotion, DEFAULT_MIN_PROMOTION_DECISIONS } from './router-promotion.js';
|
|
45
45
|
import { getMlRouter, DEFAULT_ML_K, DEFAULT_ML_MIN_SAMPLES, DEFAULT_ML_STRENGTH } from './ml-router.js';
|
|
46
46
|
import { buildModelCandidates } from './model-first-router.js';
|
|
47
47
|
import { pickBestModel, topModelCandidates } from './model-scoring.js';
|
|
48
48
|
import { getModelRegistry } from './model-registry.js';
|
|
49
|
+
import { areTwins, classifyPairEntitlement } from './pair-entitlement.js';
|
|
50
|
+
import { effectiveParameter } from './capability-evidence.js';
|
|
49
51
|
// R4: the agentic capability floor needs the same "too small to hold a tool
|
|
50
52
|
// loop" judgement the harness uses. model-harness.ts imports nothing, so this
|
|
51
53
|
// adds no cycle risk to the router.
|
|
@@ -703,7 +705,23 @@ export function analyzeTaskProfile(taskDescription) {
|
|
|
703
705
|
notes: ['content (non-code) task detected'],
|
|
704
706
|
};
|
|
705
707
|
}
|
|
706
|
-
|
|
708
|
+
// ── THE INTENT RULES MATCH WHOLE WORDS (B2 sweep) ───────────────────────
|
|
709
|
+
//
|
|
710
|
+
// These were unanchored alternations, so a FRAGMENT of an ordinary word
|
|
711
|
+
// decided the intent. Demonstrated against the built `dist`: "add a **prefix**
|
|
712
|
+
// constant to the config" and "create a **fixture** file for the tests" both
|
|
713
|
+
// classified as `debugging` (because `fix` is inside them), earning a
|
|
714
|
+
// verification pass and an openrouter escalation; "the provider **ecosystem**"
|
|
715
|
+
// classified as `architecture` (`system` inside it) with a gemini escalation;
|
|
716
|
+
// "**plant** a tree" classified as `planning`. It is the same class of defect
|
|
717
|
+
// as B2's `desc.includes('hi')` — a substring is not a word.
|
|
718
|
+
//
|
|
719
|
+
// The fix is a boundary PLUS the inflections the rule actually wants, because a
|
|
720
|
+
// bare `\b` does not help when the fragment sits at the START of a word:
|
|
721
|
+
// `\bfix\w*\b` still matches "fixture". So `fix` is spelled out
|
|
722
|
+
// (`fix|fixes|fixed|fixing|fixer|fixup`) and `plan` likewise (so `plant` is not
|
|
723
|
+
// a plan). Nothing else changed — same rules, same ORDER, same intents.
|
|
724
|
+
if (/\bmigrat(?:e|ion)\w*\b|\bupgrade\w*\b|\brefactor\w*\b|\bpipeline\w*\b|\bdeployment\w*\b/.test(text)) {
|
|
707
725
|
return {
|
|
708
726
|
intent: 'migration',
|
|
709
727
|
requiresVerification: true,
|
|
@@ -711,7 +729,8 @@ export function analyzeTaskProfile(taskDescription) {
|
|
|
711
729
|
notes: ['migration-related task detected'],
|
|
712
730
|
};
|
|
713
731
|
}
|
|
714
|
-
if (
|
|
732
|
+
if (/\barchitect\w*\b|\bdesign\w*\b|\bsystem\w*\b|\bmicroservice\w*\b|\bplatform\w*\b/.test(text) &&
|
|
733
|
+
!/\b(?:outline|plan|roadmap|strategy)\w*\b/.test(text)) {
|
|
715
734
|
return {
|
|
716
735
|
intent: 'architecture',
|
|
717
736
|
requiresVerification: true,
|
|
@@ -719,7 +738,15 @@ export function analyzeTaskProfile(taskDescription) {
|
|
|
719
738
|
notes: ['architecture-focused task detected'],
|
|
720
739
|
};
|
|
721
740
|
}
|
|
722
|
-
|
|
741
|
+
// `verif(?:y|ies|ying|ication|ications)` — the ASK forms — and deliberately NOT
|
|
742
|
+
// `verified`. Bounding this rule surfaced why: a bare `\bverif\w*\b` would also
|
|
743
|
+
// match the past participle, and "the build was verified" is a STATUS REPORT,
|
|
744
|
+
// not a verification task, so it must not buy a reviewer pass and an
|
|
745
|
+
// openrouter escalation. Measured: that narrower spelling changes the intent of
|
|
746
|
+
// ZERO of the 161 tasks in the local routing history, where `\bverif\w*\b`
|
|
747
|
+
// changed one (a past-tense status message) — so the conservative form is the
|
|
748
|
+
// one that fixes the fragment bug without moving any real decision.
|
|
749
|
+
if (/\bverif(?:y|ies|ying|ication|ications)\b|\bvalidat\w*\b|\brollout\w*\b|\bdeploy\w*\b|\bproduction\w*\b|\blaunch\w*\b|\brelease\w*\b/.test(text)) {
|
|
723
750
|
return {
|
|
724
751
|
intent: 'verification',
|
|
725
752
|
requiresVerification: true,
|
|
@@ -727,7 +754,7 @@ export function analyzeTaskProfile(taskDescription) {
|
|
|
727
754
|
notes: ['verification-related task detected'],
|
|
728
755
|
};
|
|
729
756
|
}
|
|
730
|
-
if (
|
|
757
|
+
if (/\bsecurit\w*\b|\baudit\w*\b|\bvulnerab\w*\b|\bthreat\w*\b|\bexploit\w*\b/.test(text)) {
|
|
731
758
|
return {
|
|
732
759
|
intent: 'security',
|
|
733
760
|
requiresVerification: true,
|
|
@@ -735,7 +762,7 @@ export function analyzeTaskProfile(taskDescription) {
|
|
|
735
762
|
notes: ['security-sensitive task detected'],
|
|
736
763
|
};
|
|
737
764
|
}
|
|
738
|
-
if (
|
|
765
|
+
if (/\bdebug\w*\b|\bbug\w*\b|\berror\w*\b|\bfix(?:es|ed|ing|er|up)?\b|\btrace\w*\b/.test(text)) {
|
|
739
766
|
return {
|
|
740
767
|
intent: 'debugging',
|
|
741
768
|
// C4 — a debug/build-fix task is not done until the fix is OBSERVED to
|
|
@@ -749,7 +776,7 @@ export function analyzeTaskProfile(taskDescription) {
|
|
|
749
776
|
notes: ['debugging task detected'],
|
|
750
777
|
};
|
|
751
778
|
}
|
|
752
|
-
if (
|
|
779
|
+
if (/\bplan(?:s|ned|ning|ner)?\b|\boutline\w*\b|\broadmap\w*\b|\bstrateg\w*\b/.test(text)) {
|
|
753
780
|
return {
|
|
754
781
|
intent: 'planning',
|
|
755
782
|
requiresVerification: false,
|
|
@@ -796,6 +823,14 @@ export function scoreProvider(provider, capabilities, weights) {
|
|
|
796
823
|
*/
|
|
797
824
|
export class AutoModelRouter {
|
|
798
825
|
profiles;
|
|
826
|
+
/**
|
|
827
|
+
* The attribution of the LAST recorded outcome, kept so a user correction that
|
|
828
|
+
* arrives with the next message ("still broken") can be applied to the turn it
|
|
829
|
+
* is actually about. The bandit's own `getLastProvider`/`getLastModel` are
|
|
830
|
+
* overwritten by the next resolve(), so the pair must be captured HERE, at
|
|
831
|
+
* record time — see `recordUserRejection`.
|
|
832
|
+
*/
|
|
833
|
+
pendingOutcome = null;
|
|
799
834
|
constructor(profiles) {
|
|
800
835
|
this.profiles = profiles ? { ...DEFAULT_PROFILES, ...profiles } : { ...DEFAULT_PROFILES };
|
|
801
836
|
}
|
|
@@ -827,97 +862,57 @@ export class AutoModelRouter {
|
|
|
827
862
|
/**
|
|
828
863
|
* MODEL-LEVEL capability refinement (model-first-router parity): the
|
|
829
864
|
* provider's capability profile is a BASELINE — the model actually served
|
|
830
|
-
* may be much stronger or weaker. Refines the provider caps with
|
|
831
|
-
*
|
|
865
|
+
* may be much stronger or weaker. Refines the provider caps with MEASURED
|
|
866
|
+
* evidence about the model the router will serve for THIS task (resolveModel:
|
|
832
867
|
* the configured pin, or the task-resolved best model via pickBestModel).
|
|
833
868
|
*
|
|
834
|
-
*
|
|
835
|
-
*
|
|
836
|
-
*
|
|
837
|
-
*
|
|
838
|
-
*
|
|
839
|
-
*
|
|
840
|
-
*
|
|
841
|
-
*
|
|
842
|
-
*
|
|
843
|
-
*
|
|
844
|
-
*
|
|
845
|
-
*
|
|
869
|
+
* B1/B4 (Bundle 3b, 2026-10-07) — THE NAME NO LONGER CONTRIBUTES ANYTHING.
|
|
870
|
+
*
|
|
871
|
+
* What used to be here: a fast-tier word list (`mini`/`tiny`/`small`/`nano`/
|
|
872
|
+
* `lite`/`instant`/`flash`/`turbo`/`haiku`) SUBTRACTING 0.1 per word, a
|
|
873
|
+
* slow-tier list (`large`/`max`/`opus`/`pro`/`ultra`/`frontier`) adding 0.15, a
|
|
874
|
+
* parameter-size table (`70b` +0.45 … `≤4b` −0.2), and frontier-family
|
|
875
|
+
* keywords (+0.2). All of it is REMOVED, not capped: a capped name hint is
|
|
876
|
+
* still a name judgement, and the programme's own investigation fell into the
|
|
877
|
+
* trap — it called DeepSeek V4.1 Flash weak *because of the word "flash"*
|
|
878
|
+
* while that model served an 82-step build. It also made max mode's reasoning
|
|
879
|
+
* floor a spelling test, so `gemini-3.1-flash-lite` was excluded for its name
|
|
880
|
+
* and a 70B id on a weak provider was admitted for its name.
|
|
881
|
+
*
|
|
882
|
+
* What replaces it: the MEASURED scorecard (`learning/capability-evidence.ts`),
|
|
883
|
+
* stored per provider × model in the registry. `accuracy` replaces the id
|
|
884
|
+
* reasoning hints and `performance` replaces the id speed hints, both blended
|
|
885
|
+
* against this provider's declared baseline as the cold-start PRIOR:
|
|
846
886
|
*
|
|
847
|
-
*
|
|
848
|
-
*
|
|
849
|
-
*
|
|
850
|
-
*
|
|
887
|
+
* - 0 samples → the provider baseline, byte-for-byte. Nothing that has never
|
|
888
|
+
* been measured changes behaviour, and two ids differing only by a tier
|
|
889
|
+
* word score IDENTICALLY (B1's acceptance) because the prior is
|
|
890
|
+
* provider-level and cannot see the id;
|
|
891
|
+
* - ≥ MIN_SAMPLES → the measured value, with the prior's weight decaying to
|
|
892
|
+
* zero by `PRIOR_FULL_SAMPLES`.
|
|
893
|
+
*
|
|
894
|
+
* B4 follows from that: max mode's floor is now applied to measured accuracy,
|
|
895
|
+
* so a pair is excluded for what it has DONE, not for what it is called.
|
|
851
896
|
*/
|
|
852
897
|
getModelCapabilities(provider, model) {
|
|
853
898
|
const base = { ...this.getCapabilities(provider) };
|
|
854
899
|
if (!model || model === 'default')
|
|
855
900
|
return base;
|
|
856
|
-
const m = model.toLowerCase();
|
|
857
|
-
let reasoningAdj = 0;
|
|
858
|
-
let speedAdj = 0;
|
|
859
|
-
// Parameter-size evidence. The >=60B boost is deliberately large enough to
|
|
860
|
-
// lift a genuine 70B model on a WEAK provider baseline (local 0.30 + 0.45 =
|
|
861
|
-
// 0.75) over the C4 verification reasoning floor — otherwise a capable local
|
|
862
|
-
// 70B model would be dropped for a build-debug ask while a cloud fast-lite
|
|
863
|
-
// model slips through. It never affects SCORING (this method feeds the
|
|
864
|
-
// constraint gate only), just who is eligible.
|
|
865
|
-
const params = /(\d+(?:\.\d+)?)b(?:\b|-|$)/.exec(m);
|
|
866
|
-
if (params) {
|
|
867
|
-
const b = parseFloat(params[1]);
|
|
868
|
-
if (b >= 60)
|
|
869
|
-
reasoningAdj += 0.45;
|
|
870
|
-
else if (b >= 30)
|
|
871
|
-
reasoningAdj += 0.15;
|
|
872
|
-
else if (b >= 12)
|
|
873
|
-
reasoningAdj += 0.05;
|
|
874
|
-
else if (b <= 4) {
|
|
875
|
-
reasoningAdj -= 0.2;
|
|
876
|
-
speedAdj += 0.15; // tiny models are fast
|
|
877
|
-
}
|
|
878
|
-
}
|
|
879
|
-
// Tier-word evidence. A fast-tier word LOWERS reasoning and RAISES speed;
|
|
880
|
-
// STACKED fast-tier markers are penalized PER WORD, so `gemini-3.1-flash-lite`
|
|
881
|
-
// (flash + lite) lands below the C4 verification reasoning floor while a
|
|
882
|
-
// single `-flash` id stays above it. This stacked-token penalty is what makes
|
|
883
|
-
// "don't serve a flash-lite model for a build-debug ask" expressible as a
|
|
884
|
-
// reasoning floor rather than a brittle id denylist.
|
|
885
|
-
const SLOW_TIER = /\b(large|max|opus|pro|ultra|frontier)\b/;
|
|
886
|
-
const FAST_TIER = /\b(mini|tiny|small|nano|lite|instant|flash|turbo|haiku)\b/g;
|
|
887
|
-
if (SLOW_TIER.test(m))
|
|
888
|
-
reasoningAdj += 0.15;
|
|
889
|
-
const fastTierHits = m.match(FAST_TIER)?.length ?? 0;
|
|
890
|
-
if (fastTierHits > 0) {
|
|
891
|
-
reasoningAdj -= 0.1 * fastTierHits;
|
|
892
|
-
speedAdj += 0.15;
|
|
893
|
-
}
|
|
894
|
-
// Frontier-family evidence (keyword match on the id).
|
|
895
|
-
if (/\b(gpt-[45]|o[134](?:-|$)|claude-[34]|gemini-2|deepseek-r1|qwen3|qwen-3)\b/.test(m) ||
|
|
896
|
-
/gpt-4|gpt-5|claude-3|claude-4|gemini-2/.test(m)) {
|
|
897
|
-
reasoningAdj += 0.2;
|
|
898
|
-
}
|
|
899
|
-
// Registry latency evidence: measured latency far from the provider
|
|
900
|
-
// baseline's implied speed. 0.5s ≈ fast tier, 10s+ ≈ heavyweight.
|
|
901
901
|
try {
|
|
902
|
-
const
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
}
|
|
911
|
-
}
|
|
902
|
+
const record = getModelRegistry().getCapability(provider, model);
|
|
903
|
+
const accuracy = effectiveParameter(record, 'accuracy', base.reasoning);
|
|
904
|
+
const performance = effectiveParameter(record, 'performance', base.speed);
|
|
905
|
+
return {
|
|
906
|
+
...base,
|
|
907
|
+
reasoning: Math.min(1, Math.max(0, accuracy.value)),
|
|
908
|
+
speed: Math.min(1, Math.max(0, performance.value)),
|
|
909
|
+
};
|
|
912
910
|
}
|
|
913
911
|
catch {
|
|
914
|
-
// Registry unavailable —
|
|
912
|
+
// Registry unavailable — the provider baseline stands (never a race for a
|
|
913
|
+
// capability read: the gate must behave exactly as it did before).
|
|
914
|
+
return base;
|
|
915
915
|
}
|
|
916
|
-
return {
|
|
917
|
-
...base,
|
|
918
|
-
reasoning: Math.min(1, Math.max(0, base.reasoning + reasoningAdj)),
|
|
919
|
-
speed: Math.min(1, Math.max(0, base.speed + speedAdj)),
|
|
920
|
-
};
|
|
921
916
|
}
|
|
922
917
|
/**
|
|
923
918
|
* Default candidate providers — DYNAMIC (Issue 001): every provider the user
|
|
@@ -1621,6 +1616,17 @@ export class AutoModelRouter {
|
|
|
1621
1616
|
let routedBy = 'heuristic';
|
|
1622
1617
|
let banditEscalation = false;
|
|
1623
1618
|
let escalatedProvider;
|
|
1619
|
+
/**
|
|
1620
|
+
* P6 (fix_model_routing) — did the bandit's data actually inform this pick?
|
|
1621
|
+
*
|
|
1622
|
+
* `routedBy = 'bandit'` is set the moment `useBandit` is on, and the
|
|
1623
|
+
* explanation then says `bandit-learned` — even when the arm has no samples
|
|
1624
|
+
* at all (a cold-start prior, whose constant 0.5 multiplier cannot reorder
|
|
1625
|
+
* anything) and even when the whole store is stale. The label is a claim
|
|
1626
|
+
* about evidence, so it is only made when there IS evidence: samples for the
|
|
1627
|
+
* winning arm, or a real uncertainty-driven escalation.
|
|
1628
|
+
*/
|
|
1629
|
+
let banditInformed = false;
|
|
1624
1630
|
if (options.useBandit) {
|
|
1625
1631
|
const bandit = getRouterBandit();
|
|
1626
1632
|
// v3 — the learning bucket is task-INTENT-aware. Derive the intent from
|
|
@@ -1651,6 +1657,13 @@ export class AutoModelRouter {
|
|
|
1651
1657
|
const minSamples = options.escalationMinSamples ?? DEFAULT_MIN_SAMPLES;
|
|
1652
1658
|
const winner = scored.find((s) => !s.inCooldown) || scored[0];
|
|
1653
1659
|
const winnerPrior = bandit.getPrior(winner.provider, complexity, analyzedProfile.intent);
|
|
1660
|
+
// The concrete model is resolved LATER in this method (after the model-first
|
|
1661
|
+
// override), so the verdict is made on the provider arm — the arm every
|
|
1662
|
+
// provider-level sample lands in, and the one the pre-model-first pick is
|
|
1663
|
+
// actually driven by.
|
|
1664
|
+
banditInformed =
|
|
1665
|
+
!bandit.isStale() &&
|
|
1666
|
+
bandit.hasLearnedData(winner.provider, complexity, analyzedProfile.intent);
|
|
1654
1667
|
if (winnerPrior.alpha + winnerPrior.beta < minSamples) {
|
|
1655
1668
|
// S5: never escalate DOWNWARD in capability. The bandit's learned
|
|
1656
1669
|
// priors come from past sessions (often coding-heavy local usage), so
|
|
@@ -1670,6 +1683,9 @@ export class AutoModelRouter {
|
|
|
1670
1683
|
})());
|
|
1671
1684
|
if (learnedAlternative) {
|
|
1672
1685
|
banditEscalation = true;
|
|
1686
|
+
// An escalation IS learned behaviour by construction — it fired
|
|
1687
|
+
// BECAUSE the alternative has accumulated data.
|
|
1688
|
+
banditInformed = !bandit.isStale();
|
|
1673
1689
|
escalatedProvider = learnedAlternative.provider;
|
|
1674
1690
|
if (options.verbose) {
|
|
1675
1691
|
logger.info(` 🎲 Bandit uncertainty: ${winner.provider} has no learning data (α+β=${winnerPrior.alpha + winnerPrior.beta}) — escalating to learned ${learnedAlternative.provider} (win-rate ≥ ${ESCALATION_WIN_RATE_FLOOR})`);
|
|
@@ -1806,6 +1822,78 @@ export class AutoModelRouter {
|
|
|
1806
1822
|
catch {
|
|
1807
1823
|
// Best-effort — model-first must never break routing
|
|
1808
1824
|
}
|
|
1825
|
+
// ── PAIR ENTITLEMENT: is the pick ALLOWED to be called? (D7) ────────────
|
|
1826
|
+
// The user's rule, verbatim: "suppose we have 4 providers and everyone
|
|
1827
|
+
// provides deepseek — only one provider's key actually provides deepseek,
|
|
1828
|
+
// where as the user has not purchased model access on the other providers or
|
|
1829
|
+
// there is no free tokens offered by those providers… the rank of a model can
|
|
1830
|
+
// be the same, but the agent should only pick, or only be allowed to access,
|
|
1831
|
+
// the one which has a genuine token budget available, rather than going for
|
|
1832
|
+
// rounds unnecessarily."
|
|
1833
|
+
//
|
|
1834
|
+
// Ranking cannot express that, and it should not try: rank is a property of
|
|
1835
|
+
// the MODEL, while access is a property of the ACCOUNT serving it. So the
|
|
1836
|
+
// pick made above is re-checked against the registry's OWN evidence for
|
|
1837
|
+
// exactly this pair, and when that evidence is a refusal the router looks for
|
|
1838
|
+
// the same model on a provider that can actually serve it. Two ordered
|
|
1839
|
+
// attempts, then out:
|
|
1840
|
+
// 1. a FUNDED TWIN of the same model (the reported case: identical rank,
|
|
1841
|
+
// one funded account) — chosen through `getAllUsablePairs()`, which is
|
|
1842
|
+
// built on the SAME `isUsable()` gate routing uses, so this can never
|
|
1843
|
+
// name a pair the router would itself reject;
|
|
1844
|
+
// 2. otherwise the top-ranked provider whose served pair is not refused, so
|
|
1845
|
+
// a dead pin on one provider does not cost a round trip before the
|
|
1846
|
+
// fallback walk starts.
|
|
1847
|
+
// If neither exists the original pick stands — a refusal we cannot route
|
|
1848
|
+
// around must not become "return no route at all".
|
|
1849
|
+
let entitlementRescue;
|
|
1850
|
+
try {
|
|
1851
|
+
const registry = getModelRegistry();
|
|
1852
|
+
if (classifyPairEntitlement(registry.getEntry(provider, model)) === 'refused') {
|
|
1853
|
+
const from = `${provider}/${model}`;
|
|
1854
|
+
const fundedTwin = registry
|
|
1855
|
+
.getAllUsablePairs()
|
|
1856
|
+
.find((p) => p.provider !== provider && areTwins(p.model, model));
|
|
1857
|
+
if (fundedTwin) {
|
|
1858
|
+
provider = fundedTwin.provider;
|
|
1859
|
+
model = fundedTwin.model;
|
|
1860
|
+
entitlementRescue = {
|
|
1861
|
+
from,
|
|
1862
|
+
to: `${provider}/${model}`,
|
|
1863
|
+
why: 'the pinned pair is refused by its account; the same model is funded here',
|
|
1864
|
+
};
|
|
1865
|
+
}
|
|
1866
|
+
else {
|
|
1867
|
+
const alternative = scored.find((s) => {
|
|
1868
|
+
if (s.inCooldown)
|
|
1869
|
+
return false;
|
|
1870
|
+
if (s.provider === provider)
|
|
1871
|
+
return false;
|
|
1872
|
+
try {
|
|
1873
|
+
return (classifyPairEntitlement(registry.getEntry(s.provider, this.resolveModel(s.provider, agentType, configManager, taskDescription))) !== 'refused');
|
|
1874
|
+
}
|
|
1875
|
+
catch {
|
|
1876
|
+
return false;
|
|
1877
|
+
}
|
|
1878
|
+
});
|
|
1879
|
+
if (alternative) {
|
|
1880
|
+
provider = alternative.provider;
|
|
1881
|
+
model = this.resolveModel(provider, agentType, configManager, taskDescription);
|
|
1882
|
+
entitlementRescue = {
|
|
1883
|
+
from,
|
|
1884
|
+
to: `${provider}/${model}`,
|
|
1885
|
+
why: 'the selected pair is refused by its account; this provider is not',
|
|
1886
|
+
};
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
if (entitlementRescue && options.verbose) {
|
|
1890
|
+
logger.warn(` ⛔ Pair entitlement: ${entitlementRescue.from} cannot serve this model — using ${entitlementRescue.to} (${entitlementRescue.why})`);
|
|
1891
|
+
}
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
catch {
|
|
1895
|
+
// Best-effort — the entitlement gate must never break routing.
|
|
1896
|
+
}
|
|
1809
1897
|
// Note the decision so outcome recording (recordOutcome) can reward the
|
|
1810
1898
|
// provider that actually served the task.
|
|
1811
1899
|
if (options.useBandit) {
|
|
@@ -1839,22 +1927,76 @@ export class AutoModelRouter {
|
|
|
1839
1927
|
// burning through all of its fallbacks.
|
|
1840
1928
|
const fallbackChain = [];
|
|
1841
1929
|
const seenFallback = new Set();
|
|
1930
|
+
/**
|
|
1931
|
+
* D6 — THE OFFERED CHAIN OBEYS THE SAME GATE THE PICK DOES.
|
|
1932
|
+
*
|
|
1933
|
+
* "Every provider contributes MULTIPLE models" is right, but the models it
|
|
1934
|
+
* contributed were never asked whether they could work. Measured on a live
|
|
1935
|
+
* `model explain`: the chain offered `openrouter/~deepseek/deepseek-pro-latest`
|
|
1936
|
+
* and `openrouter/~deepseek/deepseek-flash-latest` — both `unverified` alias
|
|
1937
|
+
* ids — with the same confident `Fallback (alternate model on openrouter)`
|
|
1938
|
+
* label as a proven model. A pair the registry has already proven dead
|
|
1939
|
+
* (`unavailable` — a 404, a dead key, an unfunded account) was offered the
|
|
1940
|
+
* same way, and `model list`/`models explain` are where an operator decides
|
|
1941
|
+
* what to pin next.
|
|
1942
|
+
*
|
|
1943
|
+
* Two different verdicts, two different treatments, and the distinction is
|
|
1944
|
+
* the product's own doctrine (`model-reachability.ts`):
|
|
1945
|
+
* - `unavailable` is a NO. It is not offered at all — recommending a pair a
|
|
1946
|
+
* real call already established cannot work is telling the operator to
|
|
1947
|
+
* walk into a wall.
|
|
1948
|
+
* - `unverified` is an UNKNOWN, not a failure. It stays (a weak candidate
|
|
1949
|
+
* beats no candidate, and "nothing tried yet" is exactly what a reserve
|
|
1950
|
+
* pool is for) but it is LABELLED as unproven, so the confidence in the
|
|
1951
|
+
* label matches the evidence behind it.
|
|
1952
|
+
*
|
|
1953
|
+
* The reserve passes below already label themselves this way; passes 1–3 did
|
|
1954
|
+
* not, which is where the unproven ids leaked in.
|
|
1955
|
+
*
|
|
1956
|
+
* D7 (2026-10-07) — THE PAIR'S RIGHT TO BE CALLED, not just its status.
|
|
1957
|
+
* The gate above reads `status`, and `status` cannot see the case the user
|
|
1958
|
+
* reported: four providers offer the same model, one key has access to it,
|
|
1959
|
+
* and the other three have no model access and no free tokens. A refusal like
|
|
1960
|
+
* that does NOT demote a `verified` entry (the registry keeps the latch
|
|
1961
|
+
* because a top-up repairs it), so such a pair reads `verified` with a `402`
|
|
1962
|
+
* in `lastError` — offerable by the old check. `classifyPairEntitlement`
|
|
1963
|
+
* decides from the EVIDENCE (this pair's own row) instead, and a refused pair
|
|
1964
|
+
* is not offered at all while anything else can be. Read it per PAIR: twins
|
|
1965
|
+
* on different providers are different ACCOUNTS with opposite verdicts, and
|
|
1966
|
+
* neither inherits the other's.
|
|
1967
|
+
*/
|
|
1842
1968
|
const pushFallback = (s, model, alternate) => {
|
|
1843
1969
|
const key = `${s.provider}|${model}`;
|
|
1844
1970
|
if (seenFallback.has(key))
|
|
1845
1971
|
return;
|
|
1972
|
+
let entry;
|
|
1973
|
+
try {
|
|
1974
|
+
entry = getModelRegistry().getEntry(s.provider, model);
|
|
1975
|
+
}
|
|
1976
|
+
catch {
|
|
1977
|
+
// Best-effort — the reachability gate must never break routing.
|
|
1978
|
+
}
|
|
1979
|
+
const entitlement = classifyPairEntitlement(entry);
|
|
1980
|
+
// A refusal is a NO (D7); an `unavailable` entry for any other reason is
|
|
1981
|
+
// the reachability NO the D6 gate already encoded.
|
|
1982
|
+
if (entitlement === 'refused' || entry?.status === 'unavailable')
|
|
1983
|
+
return;
|
|
1846
1984
|
seenFallback.add(key);
|
|
1985
|
+
const unproven = entitlement === 'unknown';
|
|
1986
|
+
const stalledNote = entitlement === 'stalled' ? ' (stalled — proven once, not servable right now)' : '';
|
|
1847
1987
|
fallbackChain.push({
|
|
1848
1988
|
provider: s.provider,
|
|
1849
1989
|
model,
|
|
1850
1990
|
estimatedCost: 0,
|
|
1851
1991
|
qualityScore: s.score,
|
|
1852
1992
|
contextWindowTokens: s.contextWindowTokens,
|
|
1853
|
-
reason:
|
|
1854
|
-
? `Fallback (alternate model on ${s.provider})`
|
|
1855
|
-
:
|
|
1856
|
-
? `Fallback (
|
|
1857
|
-
:
|
|
1993
|
+
reason: unproven
|
|
1994
|
+
? `Fallback (unverified — not yet proven${alternate ? `, alternate model on ${s.provider}` : `: ${s.provider}`})`
|
|
1995
|
+
: alternate
|
|
1996
|
+
? `Fallback (alternate model on ${s.provider})${stalledNote}`
|
|
1997
|
+
: s.inCooldown
|
|
1998
|
+
? `Fallback (in cooldown): ${s.provider}${stalledNote}`
|
|
1999
|
+
: `Fallback: ${s.provider}${stalledNote}`,
|
|
1858
2000
|
});
|
|
1859
2001
|
};
|
|
1860
2002
|
const modelsFor = (s) => this.fallbackModelsFor(s.provider, agentType, configManager, taskDescription, complexity);
|
|
@@ -1953,7 +2095,19 @@ export class AutoModelRouter {
|
|
|
1953
2095
|
}
|
|
1954
2096
|
const explanation = this.buildExplanation(agentType, complexity, taskType, provider, decisionScore, mode, model, weights, taskProfile) +
|
|
1955
2097
|
(weakModelForAgenticAsk ? ' | ⚠ weak-model fallback (no capable provider free)' : '') +
|
|
1956
|
-
(
|
|
2098
|
+
(entitlementRescue
|
|
2099
|
+
? ` | ⛔ ${entitlementRescue.from} is refused by its account — served by ${entitlementRescue.to}`
|
|
2100
|
+
: '') +
|
|
2101
|
+
// P6 — say what actually happened. A cold-start or stale store gets NO
|
|
2102
|
+
// claim of learning: the deterministic ranking stands, and the user is told
|
|
2103
|
+
// that instead of being told "learned" about a prior nobody has ever
|
|
2104
|
+
// updated. Silent-about-nothing would be worse still — the absence of the
|
|
2105
|
+
// old label is only honest if the reason is named.
|
|
2106
|
+
(routedBy === 'bandit'
|
|
2107
|
+
? banditInformed
|
|
2108
|
+
? ' | bandit-learned'
|
|
2109
|
+
: ' | bandit cold-start (no learned samples for this task bucket — the deterministic ranking stands)'
|
|
2110
|
+
: '') +
|
|
1957
2111
|
(banditEscalation ? ' | escalated: winner unlearned' : '') +
|
|
1958
2112
|
// ISSUE-002 explanation transparency: cite the registry data that
|
|
1959
2113
|
// excluded providers from the candidate pool, so auto routing proves
|
|
@@ -2007,6 +2161,8 @@ export class AutoModelRouter {
|
|
|
2007
2161
|
agenticCapable: isAgenticCapableModel(model, provider),
|
|
2008
2162
|
overrideReason,
|
|
2009
2163
|
banditEscalation,
|
|
2164
|
+
// P6 — whether the bandit's data informed this pick (see `banditInformed`).
|
|
2165
|
+
banditInformed,
|
|
2010
2166
|
governanceBlocked,
|
|
2011
2167
|
registryExcluded,
|
|
2012
2168
|
contextPreflight,
|
|
@@ -2077,6 +2233,15 @@ export class AutoModelRouter {
|
|
|
2077
2233
|
* @param outcome success | failure | escalated
|
|
2078
2234
|
* @param configManager Optional — used to resolve per-provider pricing
|
|
2079
2235
|
* overrides when computing the cost-adjusted reward
|
|
2236
|
+
* @param outcomeData The MEASURED outcome telemetry for the reward model —
|
|
2237
|
+
* `testPassed` / `userAccepted` / `verificationPassed` /
|
|
2238
|
+
* `qualityScore`. Typed as the bandit's own
|
|
2239
|
+
* `Partial<BanditOutcomeData>` so the harness cannot
|
|
2240
|
+
* collect a signal this path has no parameter for: the
|
|
2241
|
+
* three honesty fields previously had nowhere to go, so
|
|
2242
|
+
* every caller passed `undefined` and the reward model
|
|
2243
|
+
* they feed — the ONLY measured-quality input the router
|
|
2244
|
+
* has — was dead on the real path.
|
|
2080
2245
|
*/
|
|
2081
2246
|
recordOutcome(agentType, taskDescription, outcome, configManager, outcomeData, complexityHint) {
|
|
2082
2247
|
const bandit = getRouterBandit();
|
|
@@ -2090,20 +2255,20 @@ export class AutoModelRouter {
|
|
|
2090
2255
|
// versa. Mirrors the select-time derivation exactly.
|
|
2091
2256
|
const learnIntent = analyzeTaskProfile(taskDescription).intent;
|
|
2092
2257
|
if (complexityHint) {
|
|
2093
|
-
bandit.recordOutcomeWithComplexity(provider, complexityHint, outcome, costScore,
|
|
2258
|
+
bandit.recordOutcomeWithComplexity(provider, complexityHint, outcome, costScore, outcomeData, learnIntent);
|
|
2094
2259
|
}
|
|
2095
2260
|
else {
|
|
2096
|
-
bandit.recordOutcome(provider, taskDescription, outcome, costScore,
|
|
2261
|
+
bandit.recordOutcome(provider, taskDescription, outcome, costScore, outcomeData, learnIntent);
|
|
2097
2262
|
}
|
|
2098
2263
|
// Per-modelId learning: attribute the same outcome to the concrete model
|
|
2099
2264
|
// that served the task (ruflo ADR-149 mirror) so the model choice learns.
|
|
2100
2265
|
const model = bandit.getLastModel(agentType);
|
|
2101
2266
|
if (model) {
|
|
2102
2267
|
if (complexityHint) {
|
|
2103
|
-
bandit.recordModelOutcomeWithComplexity(model, complexityHint, outcome, costScore,
|
|
2268
|
+
bandit.recordModelOutcomeWithComplexity(model, complexityHint, outcome, costScore, outcomeData, learnIntent);
|
|
2104
2269
|
}
|
|
2105
2270
|
else {
|
|
2106
|
-
bandit.recordModelOutcome(model, taskDescription, outcome, costScore,
|
|
2271
|
+
bandit.recordModelOutcome(model, taskDescription, outcome, costScore, outcomeData, learnIntent);
|
|
2107
2272
|
}
|
|
2108
2273
|
}
|
|
2109
2274
|
// Promotion gate: finalize the parallel A/B decision with the real outcome
|
|
@@ -2126,6 +2291,46 @@ export class AutoModelRouter {
|
|
|
2126
2291
|
catch {
|
|
2127
2292
|
// Best-effort — ML recording must never break outcome handling.
|
|
2128
2293
|
}
|
|
2294
|
+
// Remember WHAT this outcome was attributed to, so a correction arriving with
|
|
2295
|
+
// the NEXT user message lands on the turn it is about rather than on whoever
|
|
2296
|
+
// serves the next turn. Overwritten by the next recordOutcome, which is exactly
|
|
2297
|
+
// right: a correction speaks about the most recent judged turn.
|
|
2298
|
+
this.pendingOutcome = {
|
|
2299
|
+
agentType,
|
|
2300
|
+
provider,
|
|
2301
|
+
model: bandit.getLastModel(agentType),
|
|
2302
|
+
complexity: complexityHint ?? analyzeComplexity(taskDescription),
|
|
2303
|
+
intent: analyzeTaskProfile(taskDescription).intent,
|
|
2304
|
+
outcome,
|
|
2305
|
+
};
|
|
2306
|
+
}
|
|
2307
|
+
/**
|
|
2308
|
+
* Apply the user's verdict on the PREVIOUS turn.
|
|
2309
|
+
*
|
|
2310
|
+
* A regression report in the user's next message ("still broken", "no change",
|
|
2311
|
+
* "doesn't work") IS `userAccepted: false` for the turn that just ran — the one
|
|
2312
|
+
* measured-quality input the reward model has that nothing on the real path ever
|
|
2313
|
+
* filled in. It cannot be part of that turn's own observation (it does not exist
|
|
2314
|
+
* until the user speaks again), so it is applied here, to the pair captured at
|
|
2315
|
+
* record time, as the exact deferred delta (see `RouterBandit.recordUserRejection`).
|
|
2316
|
+
*
|
|
2317
|
+
* @returns True when a correction was applied to a still-pending outcome.
|
|
2318
|
+
*/
|
|
2319
|
+
recordUserRejection(agentType) {
|
|
2320
|
+
const pending = this.pendingOutcome;
|
|
2321
|
+
if (!pending || pending.agentType !== agentType)
|
|
2322
|
+
return false;
|
|
2323
|
+
// Clear FIRST: one correction must never be applied twice, even if the bandit
|
|
2324
|
+
// write throws.
|
|
2325
|
+
this.pendingOutcome = null;
|
|
2326
|
+
try {
|
|
2327
|
+
getRouterBandit().recordUserRejection(pending.provider, pending.complexity, pending.outcome, pending.intent, pending.model);
|
|
2328
|
+
}
|
|
2329
|
+
catch {
|
|
2330
|
+
// Learning is best-effort — never break a turn on a bandit error.
|
|
2331
|
+
return false;
|
|
2332
|
+
}
|
|
2333
|
+
return true;
|
|
2129
2334
|
}
|
|
2130
2335
|
/**
|
|
2131
2336
|
* Choose the concrete model within the selected provider using per-model
|