agent-nuvira 3.3.12 → 3.3.14
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/agents/reasoner.d.ts.map +1 -1
- package/dist/agents/agents/reasoner.js +13 -0
- package/dist/agents/agents/reasoner.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 +103 -135
- 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 +798 -39
- package/dist/cli/chat.js.map +1 -1
- package/dist/cli/cli-program.d.ts.map +1 -1
- package/dist/cli/cli-program.js +20 -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 +107 -3
- 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/execute.d.ts.map +1 -1
- package/dist/cli/execute.js +6 -0
- package/dist/cli/execute.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 +31 -0
- package/dist/cli/knowledge.d.ts.map +1 -0
- package/dist/cli/knowledge.js +223 -0
- package/dist/cli/knowledge.js.map +1 -0
- package/dist/cli/loop-executor.d.ts.map +1 -1
- package/dist/cli/loop-executor.js +92 -18
- 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/omniroute-control.d.ts +88 -0
- package/dist/cli/omniroute-control.d.ts.map +1 -0
- package/dist/cli/omniroute-control.js +179 -0
- package/dist/cli/omniroute-control.js.map +1 -0
- package/dist/cli/omniroute.d.ts +21 -0
- package/dist/cli/omniroute.d.ts.map +1 -0
- package/dist/cli/omniroute.js +67 -0
- package/dist/cli/omniroute.js.map +1 -0
- 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/config/types.d.ts +8 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/context/history.d.ts +21 -0
- package/dist/context/history.d.ts.map +1 -1
- package/dist/context/history.js +50 -0
- package/dist/context/history.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 +206 -0
- package/dist/gateway/hook-contract.d.ts.map +1 -0
- package/dist/gateway/hook-contract.js +449 -0
- package/dist/gateway/hook-contract.js.map +1 -0
- package/dist/gateway/hooks.d.ts.map +1 -1
- package/dist/gateway/hooks.js +14 -0
- package/dist/gateway/hooks.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 +54 -0
- package/dist/inference/interface.d.ts.map +1 -1
- package/dist/inference/model-id-validation.d.ts +57 -0
- package/dist/inference/model-id-validation.d.ts.map +1 -0
- package/dist/inference/model-id-validation.js +127 -0
- package/dist/inference/model-id-validation.js.map +1 -0
- 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/openai-compat-adapter.d.ts.map +1 -1
- package/dist/inference/openai-compat-adapter.js +12 -1
- package/dist/inference/openai-compat-adapter.js.map +1 -1
- package/dist/inference/provider-catalog.d.ts +11 -0
- package/dist/inference/provider-catalog.d.ts.map +1 -1
- package/dist/inference/provider-catalog.js +80 -0
- package/dist/inference/provider-catalog.js.map +1 -1
- package/dist/inference/route-resolver.d.ts +32 -8
- package/dist/inference/route-resolver.d.ts.map +1 -1
- package/dist/inference/route-resolver.js +175 -1
- package/dist/inference/route-resolver.js.map +1 -1
- package/dist/inference/tool-call-utils.d.ts.map +1 -1
- package/dist/inference/tool-call-utils.js +10 -8
- package/dist/inference/tool-call-utils.js.map +1 -1
- package/dist/inference/tools.d.ts +17 -1
- package/dist/inference/tools.d.ts.map +1 -1
- package/dist/inference/tools.js +69 -19
- 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 +121 -24
- package/dist/learning/auto-router.d.ts.map +1 -1
- package/dist/learning/auto-router.js +374 -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 +45 -2
- package/dist/learning/error-repair.d.ts.map +1 -1
- package/dist/learning/error-repair.js +95 -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 +350 -0
- package/dist/learning/knowledge-base.d.ts.map +1 -0
- package/dist/learning/knowledge-base.js +906 -0
- package/dist/learning/knowledge-base.js.map +1 -0
- 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 +2 -1
- package/dist/learning/model-selection.d.ts.map +1 -1
- package/dist/learning/model-selection.js +76 -2
- 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/project-docs.d.ts +83 -0
- package/dist/learning/project-docs.d.ts.map +1 -0
- package/dist/learning/project-docs.js +132 -0
- package/dist/learning/project-docs.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/quota-ledger.d.ts +13 -0
- package/dist/learning/quota-ledger.d.ts.map +1 -1
- package/dist/learning/quota-ledger.js +28 -0
- package/dist/learning/quota-ledger.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/seeded-benchmark.d.ts +14 -0
- package/dist/learning/seeded-benchmark.d.ts.map +1 -1
- package/dist/learning/seeded-benchmark.js +43 -4
- package/dist/learning/seeded-benchmark.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/security/secret-scan.d.ts +156 -0
- package/dist/security/secret-scan.d.ts.map +1 -0
- package/dist/security/secret-scan.js +525 -0
- package/dist/security/secret-scan.js.map +1 -0
- 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 +44 -4
- package/dist/tools/plan-store.d.ts.map +1 -1
- package/dist/tools/plan-store.js +93 -9
- 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 +623 -15
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/remediation-ladder.d.ts +91 -0
- package/dist/tools/remediation-ladder.d.ts.map +1 -0
- package/dist/tools/remediation-ladder.js +316 -0
- package/dist/tools/remediation-ladder.js.map +1 -0
- 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 +289 -10
- package/dist/tools/run-terminal.js.map +1 -1
- package/dist/tools/skills-hub.d.ts +7 -0
- package/dist/tools/skills-hub.d.ts.map +1 -1
- package/dist/tools/skills-hub.js +11 -2
- package/dist/tools/skills-hub.js.map +1 -1
- package/dist/tools/step-artifact.d.ts +35 -0
- package/dist/tools/step-artifact.d.ts.map +1 -0
- package/dist/tools/step-artifact.js +154 -0
- package/dist/tools/step-artifact.js.map +1 -0
- package/dist/tools/tool-loop.d.ts +327 -13
- package/dist/tools/tool-loop.d.ts.map +1 -1
- package/dist/tools/tool-loop.js +891 -66
- package/dist/tools/tool-loop.js.map +1 -1
- package/dist/tools/toolsets.js +4 -4
- package/dist/tools/toolsets.js.map +1 -1
- package/dist/tools/worktree.d.ts +11 -2
- package/dist/tools/worktree.d.ts.map +1 -1
- package/dist/tools/worktree.js +11 -2
- package/dist/tools/worktree.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 +40 -0
- package/dist/web-dashboard/server.d.ts.map +1 -1
- package/dist/web-dashboard/server.js +733 -15
- package/dist/web-dashboard/server.js.map +1 -1
- package/dist/web-dashboard/src/types.d.ts +236 -1
- package/dist/web-dashboard/src/types.d.ts.map +1 -1
- package/dist/web-dashboard/workspace-guard.d.ts +18 -2
- package/dist/web-dashboard/workspace-guard.d.ts.map +1 -1
- package/dist/web-dashboard/workspace-guard.js +94 -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-tK8Y2qHB.css +1 -0
- 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 +2 -2
- package/src/web-dashboard/public/assets/index-BrZcYhy6.js +0 -207
- package/src/web-dashboard/public/assets/index-BrZcYhy6.js.map +0 -1
- package/src/web-dashboard/public/assets/index-DsWczTa6.css +0 -1
|
@@ -37,10 +37,12 @@
|
|
|
37
37
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
38
38
|
import { envBuff, resolveNuviraHome } from '../config/paths.js';
|
|
39
39
|
import { formatCount } from '../utils/format.js';
|
|
40
|
+
import { stableJson, withFileLockSync, writeFileAtomicSync } from '../utils/atomic-store.js';
|
|
40
41
|
import { join } from 'node:path';
|
|
41
42
|
import { getVectorStore } from '../memory/vector-store.js';
|
|
42
43
|
import { reasoningCapabilityFromAdvertised } from '../inference/reasoning-effort.js';
|
|
43
44
|
import { isNonChatModel } from '../inference/model-catalog.js';
|
|
45
|
+
import { emptyCapabilityRecord, foldCallOutcome, foldLatency, foldVerification, } from './capability-evidence.js';
|
|
44
46
|
import { getQuotaLedger } from './quota-ledger.js';
|
|
45
47
|
import { getEventBus, EventNames } from '../observability/event-bus.js';
|
|
46
48
|
import { appendChainedRecordFast, rechainRecords, writeHeadState, headOfLines, } from '../enterprise/audit-chain.js';
|
|
@@ -73,6 +75,14 @@ export const DEGRADED_UNAVAILABLE_THRESHOLD = 3;
|
|
|
73
75
|
* permanently penalizes a recovered model in preferredModelsFor().
|
|
74
76
|
*/
|
|
75
77
|
export const ERROR_RATE_HEAL_STEP = 0.1;
|
|
78
|
+
/**
|
|
79
|
+
* How long a pair that resolved with an UNUSABLE (empty) response is parked.
|
|
80
|
+
*
|
|
81
|
+
* fix_model_routing P1/P2. Short enough to self-heal (a genuinely transient
|
|
82
|
+
* empty), long enough that the same broken pair is not re-picked on the next
|
|
83
|
+
* turn. Model-scoped: the provider's healthy siblings stay routable.
|
|
84
|
+
*/
|
|
85
|
+
export const EMPTY_RESPONSE_PARK_MS = 120_000;
|
|
76
86
|
function memoryDir() {
|
|
77
87
|
return envBuff('MEMORY_DIR') || DEFAULT_MEMORY_DIR;
|
|
78
88
|
}
|
|
@@ -114,13 +124,6 @@ function actionLogPath() {
|
|
|
114
124
|
function entryKey(provider, model) {
|
|
115
125
|
return `${provider}|${model || 'default'}`;
|
|
116
126
|
}
|
|
117
|
-
/**
|
|
118
|
-
* Does an `unavailable` REASON mean the provider/model pair cannot exist?
|
|
119
|
-
*
|
|
120
|
-
* Deliberately narrow: it must be an explicit "this model does not exist on
|
|
121
|
-
* this endpoint", never a permission/per-account answer (a 403 or a quota
|
|
122
|
-
* denial can be repaired, so the pair must stay reachable).
|
|
123
|
-
*/
|
|
124
127
|
/**
|
|
125
128
|
* Does an `unavailable` REASON mean the provider/model pair cannot exist?
|
|
126
129
|
*
|
|
@@ -130,19 +133,59 @@ function entryKey(provider, model) {
|
|
|
130
133
|
*/
|
|
131
134
|
const MODEL_NOT_FOUND_REASON_RE = /model not found|not in live model list|does not exist|no such model|model_not_found/i;
|
|
132
135
|
/**
|
|
133
|
-
* Is this entry a
|
|
136
|
+
* Is this entry a NONEXISTENT pair — the provider cannot serve this id at all?
|
|
134
137
|
*
|
|
135
138
|
* The flag is the primary signal, but the reason check heals data written
|
|
136
139
|
* BEFORE the flag existed (and by any surface): an entry sitting at
|
|
137
140
|
* `unavailable` with a not-found reason IS a retired pair, and without this the
|
|
138
141
|
* live `local/gemini-3.1-flash-lite` entry would keep being offered until it
|
|
139
142
|
* failed one more time.
|
|
143
|
+
*
|
|
144
|
+
* EXPORTED for `learning/pair-entitlement.ts`, which needs the SAME verdict the
|
|
145
|
+
* dead-pair machinery uses. Two predicates for one question is how the registry
|
|
146
|
+
* and the router start disagreeing about which pairs are offerable.
|
|
140
147
|
*/
|
|
141
|
-
function
|
|
148
|
+
export function isNonexistentPair(e) {
|
|
142
149
|
if (e.deadPair)
|
|
143
150
|
return true;
|
|
144
151
|
return e.status === 'unavailable' && MODEL_NOT_FOUND_REASON_RE.test(e.lastError ?? '');
|
|
145
152
|
}
|
|
153
|
+
/**
|
|
154
|
+
* Does an `unavailable` REASON describe an ACCOUNT-level refusal that a fresh
|
|
155
|
+
* catalogue listing cannot refute?
|
|
156
|
+
*
|
|
157
|
+
* The complement of {@link MODEL_NOT_FOUND_REASON_RE}, and deliberately disjoint
|
|
158
|
+
* from it. "This model does not exist on this endpoint" IS refuted by the
|
|
159
|
+
* provider listing the model again — but "this account cannot pay for this
|
|
160
|
+
* call" and "this key is not authorized" are facts about the ACCOUNT, and a
|
|
161
|
+
* public model list says nothing about them. Collapsing the two is what let a
|
|
162
|
+
* metadata probe re-arm a pair `recordCall` had already proven dead (see the
|
|
163
|
+
* note in `markListed`).
|
|
164
|
+
*
|
|
165
|
+
* Narrow on purpose: transient refusals (`rate-limit`, quota parks, 5xx) are
|
|
166
|
+
* NOT entitlement failures — a listing, a park expiry or a recovered key may
|
|
167
|
+
* legitimately re-open those, and `quotaParkedUntil` already models the clock.
|
|
168
|
+
*
|
|
169
|
+
* EXPORTED for `learning/pair-entitlement.ts` — see `isNonexistentPair`.
|
|
170
|
+
*/
|
|
171
|
+
export function isEntitlementFailure(lastError) {
|
|
172
|
+
if (!lastError)
|
|
173
|
+
return false;
|
|
174
|
+
if (MODEL_NOT_FOUND_REASON_RE.test(lastError))
|
|
175
|
+
return false;
|
|
176
|
+
const t = lastError.toLowerCase();
|
|
177
|
+
return (t.includes('credit-exhausted') ||
|
|
178
|
+
t.includes('insufficient credit') ||
|
|
179
|
+
t.includes('payment required') ||
|
|
180
|
+
t.includes('billing') ||
|
|
181
|
+
t.includes('unauthorized') ||
|
|
182
|
+
t.includes('forbidden') ||
|
|
183
|
+
t.includes('invalid api key') ||
|
|
184
|
+
t.includes('invalid_api_key') ||
|
|
185
|
+
t.includes('authentication failed') ||
|
|
186
|
+
/\bauth\b/.test(t) ||
|
|
187
|
+
/\b40[123]\b/.test(t));
|
|
188
|
+
}
|
|
146
189
|
/**
|
|
147
190
|
* The config value `'default'` is a SENTINEL meaning "use the provider's
|
|
148
191
|
* default model" — it is NOT a model id. Telemetry must never track it: a
|
|
@@ -156,6 +199,71 @@ export function isSentinelModel(model) {
|
|
|
156
199
|
function emptyState() {
|
|
157
200
|
return { version: CURRENT_VERSION, entries: {}, updatedAt: Date.now() };
|
|
158
201
|
}
|
|
202
|
+
/** A deep copy of registry state, so a snapshot cannot be mutated through aliasing. */
|
|
203
|
+
function cloneRegistryData(data) {
|
|
204
|
+
return JSON.parse(JSON.stringify(data));
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Merge three views of the registry into the one that should be on disk.
|
|
208
|
+
*
|
|
209
|
+
* The problem it solves, measured (2026-10-07): the singleton loads the mirror
|
|
210
|
+
* once at construction and `persist()` writes its WHOLE entry map back, so a
|
|
211
|
+
* long-lived process flushes a snapshot of every model it never touched —
|
|
212
|
+
* silently reverting what the dashboard, the gateway or a CLI run verified in
|
|
213
|
+
* the meantime. On this machine that flipped a strict-pin verdict from
|
|
214
|
+
* `credit-exhausted` back to `unverified` between two runs of one command, which
|
|
215
|
+
* re-armed the exact pre-flight F6 had just closed.
|
|
216
|
+
*
|
|
217
|
+
* The resolution needs no bookkeeping in the mutators, because `boot` already
|
|
218
|
+
* records what this process was told:
|
|
219
|
+
*
|
|
220
|
+
* - an entry this process CHANGED since boot (`memory` differs from `boot`) is
|
|
221
|
+
* this process's knowledge, and wins;
|
|
222
|
+
* - an entry it never touched is whatever `disk` now holds, because another
|
|
223
|
+
* process may have learned something about it;
|
|
224
|
+
* - an entry present at boot but ABSENT from `memory` was deliberately dropped
|
|
225
|
+
* (`pruneAbsentModels`), so the deletion wins — otherwise the prune would
|
|
226
|
+
* undo itself the moment the next persist re-added it from disk;
|
|
227
|
+
* - an entry in neither `boot` nor `memory` but on `disk` is a peer's new row,
|
|
228
|
+
* and is adopted.
|
|
229
|
+
*
|
|
230
|
+
* Comparing with `stableJson` (not a bare deep-equal) is deliberate: two entries
|
|
231
|
+
* built by different code paths can be structurally identical yet serialize with
|
|
232
|
+
* different key order, which would read as "changed" and clobber the peer.
|
|
233
|
+
*/
|
|
234
|
+
export function mergeRegistryMirror(boot, memory, disk) {
|
|
235
|
+
const entries = {};
|
|
236
|
+
const keys = new Set([
|
|
237
|
+
...Object.keys(boot.entries),
|
|
238
|
+
...Object.keys(memory.entries),
|
|
239
|
+
...Object.keys(disk.entries),
|
|
240
|
+
]);
|
|
241
|
+
for (const key of keys) {
|
|
242
|
+
const mine = memory.entries[key];
|
|
243
|
+
const base = boot.entries[key];
|
|
244
|
+
const theirs = disk.entries[key];
|
|
245
|
+
if (!mine) {
|
|
246
|
+
if (base)
|
|
247
|
+
continue; // this process removed it — the deletion is the update
|
|
248
|
+
if (theirs)
|
|
249
|
+
entries[key] = theirs; // a peer's new row
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
const changedHere = !base || stableJson(mine) !== stableJson(base);
|
|
253
|
+
if (changedHere) {
|
|
254
|
+
entries[key] = mine;
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
// Unchanged HERE, so the file decides — including its ABSENCE. Re-adding a
|
|
258
|
+
// key the file no longer has would resurrect a peer's deletion (a prune, or
|
|
259
|
+
// a `reset`), which is the same lost-update bug pointing the other way.
|
|
260
|
+
if (theirs === undefined)
|
|
261
|
+
continue;
|
|
262
|
+
entries[key] = theirs;
|
|
263
|
+
}
|
|
264
|
+
// Scalars (version, updatedAt) come from this process; only the entry map merges.
|
|
265
|
+
return { ...disk, ...memory, entries };
|
|
266
|
+
}
|
|
159
267
|
/**
|
|
160
268
|
* Aggregate raw action-telemetry entries into the per-action dashboard view.
|
|
161
269
|
* Pure + sync — the dashboard server calls this on the raw JSONL lines, and
|
|
@@ -303,6 +411,13 @@ export function aggregateActionTelemetry(entries, options = {}) {
|
|
|
303
411
|
*/
|
|
304
412
|
export class ModelRegistry {
|
|
305
413
|
data;
|
|
414
|
+
/**
|
|
415
|
+
* The registry as this process loaded it, kept so `persist()` can tell WHICH
|
|
416
|
+
* entries this process actually changed (see `mergeRegistryMirror`). Without
|
|
417
|
+
* it, a persist flushes every entry it merely READ back over whatever other
|
|
418
|
+
* processes have learned since — the measured lost-update bug.
|
|
419
|
+
*/
|
|
420
|
+
bootSnapshot;
|
|
306
421
|
/** Cached VectorStore for the enterprise mirror (null until first mirror). */
|
|
307
422
|
vectorStore = null;
|
|
308
423
|
/** Whether the vector mirror has been confirmed usable. */
|
|
@@ -311,6 +426,7 @@ export class ModelRegistry {
|
|
|
311
426
|
actionLogCount = -1;
|
|
312
427
|
constructor() {
|
|
313
428
|
this.data = this.loadMirror();
|
|
429
|
+
this.bootSnapshot = cloneRegistryData(this.data);
|
|
314
430
|
}
|
|
315
431
|
// ─── Synchronous read path (lightning fast — no I/O, no network) ─────────
|
|
316
432
|
/**
|
|
@@ -385,7 +501,7 @@ export class ModelRegistry {
|
|
|
385
501
|
getDeadPairs() {
|
|
386
502
|
const out = [];
|
|
387
503
|
for (const e of Object.values(this.data.entries)) {
|
|
388
|
-
if (
|
|
504
|
+
if (isNonexistentPair(e))
|
|
389
505
|
out.push({ provider: e.provider, model: e.model });
|
|
390
506
|
}
|
|
391
507
|
return out;
|
|
@@ -395,7 +511,7 @@ export class ModelRegistry {
|
|
|
395
511
|
if (!model || model === 'default')
|
|
396
512
|
return false;
|
|
397
513
|
const entry = this.data.entries[entryKey(provider, model)];
|
|
398
|
-
return !!entry &&
|
|
514
|
+
return !!entry && isNonexistentPair(entry);
|
|
399
515
|
}
|
|
400
516
|
/** Providers that currently have at least one verified, usable model. Sync. */
|
|
401
517
|
getUsableProviders(now = Date.now()) {
|
|
@@ -497,6 +613,23 @@ export class ModelRegistry {
|
|
|
497
613
|
getEntry(provider, model) {
|
|
498
614
|
return this.data.entries[entryKey(provider, model)];
|
|
499
615
|
}
|
|
616
|
+
/**
|
|
617
|
+
* Every verified, usable pair across ALL providers (A1).
|
|
618
|
+
*
|
|
619
|
+
* `getVerifiedModels(provider)` answers "what works on THIS provider"; this
|
|
620
|
+
* answers "what works anywhere", which is the question a pin pre-flight has to
|
|
621
|
+
* ask: when a pair is refused, the useful next sentence is "the same model is
|
|
622
|
+
* verified on <other provider>". Built on the SAME `isUsable()` gate as
|
|
623
|
+
* routing, so a suggestion here can never name a pair the router would reject.
|
|
624
|
+
*
|
|
625
|
+
* The `'default'` sentinel is excluded — it is a config marker ("use the
|
|
626
|
+
* provider's default"), not a model id, and suggesting it would be nonsense.
|
|
627
|
+
*/
|
|
628
|
+
getAllUsablePairs(now = Date.now()) {
|
|
629
|
+
return Object.values(this.data.entries)
|
|
630
|
+
.filter((e) => !isSentinelModel(e.model) && this.isUsable(e.provider, e.model, now))
|
|
631
|
+
.map((e) => ({ provider: e.provider, model: e.model }));
|
|
632
|
+
}
|
|
500
633
|
/**
|
|
501
634
|
* The learned reasoning-parameter support for a provider × model, or
|
|
502
635
|
* `undefined` when nothing has been established (callers treat absent as
|
|
@@ -593,7 +726,9 @@ export class ModelRegistry {
|
|
|
593
726
|
}
|
|
594
727
|
/**
|
|
595
728
|
* listModels probe: mark the model as seen (unverified unless already
|
|
596
|
-
* verified). Does NOT downgrade a verified entry — real verification wins
|
|
729
|
+
* verified). Does NOT downgrade a verified entry — real verification wins —
|
|
730
|
+
* and does NOT clear a definitive ACCOUNT-level refusal (see
|
|
731
|
+
* `isEntitlementFailure`).
|
|
597
732
|
* Accepts either bare ids (legacy callers) or full model descriptors; when
|
|
598
733
|
* a descriptor carries the provider-advertised context window, it is
|
|
599
734
|
* recorded so the router's context preflight can use the LIVE value.
|
|
@@ -623,10 +758,24 @@ export class ModelRegistry {
|
|
|
623
758
|
existing.reasoningCapability = reasoningCapability;
|
|
624
759
|
continue;
|
|
625
760
|
}
|
|
761
|
+
// ── A DEFINITIVE ENTITLEMENT REFUSAL SURVIVES A CATALOGUE LISTING ───────
|
|
762
|
+
// Being in the provider's model list proves the provider SERVES the model.
|
|
763
|
+
// It proves nothing about whether THIS ACCOUNT may use it — and those are
|
|
764
|
+
// different axes, the same distinction `errorRate`/`partialRate` already
|
|
765
|
+
// draw below. Measured (2026-10-06): `openrouter|deepseek/deepseek-v4.1-flash`
|
|
766
|
+
// is in OpenRouter's public catalogue, while the account behind the key had
|
|
767
|
+
// never purchased credits. `recordCall` had learned the definitive
|
|
768
|
+
// `credit-exhausted` verdict; a catalogue refresh re-listed the provider,
|
|
769
|
+
// this loop reset the entry to `unverified`, and the pin that one run
|
|
770
|
+
// refused before any network call was offered again by the next — which got
|
|
771
|
+
// a raw `402 Insufficient credits` body back. Same pin, same registry,
|
|
772
|
+
// opposite outcomes, decided by a metadata probe. An entitlement verdict
|
|
773
|
+
// only real traffic can clear.
|
|
774
|
+
const entitled = existing?.status === 'unavailable' && isEntitlementFailure(existing?.lastError);
|
|
626
775
|
this.data.entries[key] = {
|
|
627
776
|
provider,
|
|
628
777
|
model,
|
|
629
|
-
status: 'unverified',
|
|
778
|
+
status: entitled ? 'unavailable' : 'unverified',
|
|
630
779
|
lastVerifiedAt: existing?.lastVerifiedAt || 0,
|
|
631
780
|
lastProbedAt: now,
|
|
632
781
|
lastUsedAt: existing?.lastUsedAt || 0,
|
|
@@ -642,6 +791,7 @@ export class ModelRegistry {
|
|
|
642
791
|
quotaParkedUntil: existing?.quotaParkedUntil || 0,
|
|
643
792
|
reasoningCapability,
|
|
644
793
|
source: 'probe',
|
|
794
|
+
capability: existing?.capability,
|
|
645
795
|
lastError: existing?.lastError,
|
|
646
796
|
// The provider's OWN model list is authoritative about what it serves:
|
|
647
797
|
// a pair that appears in a fresh list is not a dead pair, so the flag is
|
|
@@ -718,6 +868,11 @@ export class ModelRegistry {
|
|
|
718
868
|
// Reasoning-parameter support is model metadata, independent of the verify
|
|
719
869
|
// event — it survives a re-verify exactly like contextWindowTokens does.
|
|
720
870
|
reasoningCapability: existing?.reasoningCapability,
|
|
871
|
+
// Bundle 3b: the measured scorecard is model metadata too. A verification
|
|
872
|
+
// proves the pair ANSWERS; it says nothing about how well it does the work,
|
|
873
|
+
// so the accuracy samples must not be reset by it (nor by any availability
|
|
874
|
+
// flip — see markListed/markUnavailable).
|
|
875
|
+
capability: existing?.capability,
|
|
721
876
|
};
|
|
722
877
|
this.persist();
|
|
723
878
|
// A GENUINE promotion (was not verified → now verified) is a state change
|
|
@@ -734,6 +889,60 @@ export class ModelRegistry {
|
|
|
734
889
|
this.appendActionLog({ timestamp: now, action, provider, model, outcome: 'verified', latencyMs, costUsd, callId });
|
|
735
890
|
}
|
|
736
891
|
}
|
|
892
|
+
/**
|
|
893
|
+
* Fold one observation into this pair's capability scorecard (Bundle 3b).
|
|
894
|
+
*
|
|
895
|
+
* Silent and best-effort by design: a scorecard write must never fail a call or
|
|
896
|
+
* an availability write. The record is model metadata — created on first
|
|
897
|
+
* observation, never reset by an availability flip (see `markVerified`).
|
|
898
|
+
*/
|
|
899
|
+
foldCapability(provider, model, observation) {
|
|
900
|
+
if (isSentinelModel(model))
|
|
901
|
+
return;
|
|
902
|
+
try {
|
|
903
|
+
const key = entryKey(provider, model);
|
|
904
|
+
const entry = this.data.entries[key];
|
|
905
|
+
if (!entry)
|
|
906
|
+
return;
|
|
907
|
+
const now = observation.now ?? Date.now();
|
|
908
|
+
let record = entry.capability ?? emptyCapabilityRecord(now);
|
|
909
|
+
if (observation.verification !== undefined) {
|
|
910
|
+
record = foldVerification(record, observation.verification, now);
|
|
911
|
+
}
|
|
912
|
+
if (observation.ok !== undefined) {
|
|
913
|
+
record = foldCallOutcome(record, observation.ok, now);
|
|
914
|
+
}
|
|
915
|
+
if (observation.latencyMs !== undefined) {
|
|
916
|
+
record = foldLatency(record, observation.latencyMs, now);
|
|
917
|
+
}
|
|
918
|
+
if (record === entry.capability)
|
|
919
|
+
return;
|
|
920
|
+
entry.capability = record;
|
|
921
|
+
this.persist();
|
|
922
|
+
}
|
|
923
|
+
catch {
|
|
924
|
+
// Best-effort — the scorecard is learning, never a gate.
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
/**
|
|
928
|
+
* Record a completed TURN's verification verdict for a pair (Bundle 3b).
|
|
929
|
+
*
|
|
930
|
+
* The accuracy parameter's real feed: `TurnReport.verification` is derived from
|
|
931
|
+
* recorded tool/plan evidence and cannot be talked up by the model, so the
|
|
932
|
+
* sample this writes is an observation rather than a claim. `blocked` and
|
|
933
|
+
* `not-applicable` contribute NOTHING — a wall the run hit, and a turn with
|
|
934
|
+
* nothing to check, are both silent about the model.
|
|
935
|
+
*
|
|
936
|
+
* A pair with no entry yet is skipped rather than invented: the scorecard hangs
|
|
937
|
+
* off a registry row, and creating one from a turn would claim the pair exists.
|
|
938
|
+
*/
|
|
939
|
+
recordCapabilityEvidence(provider, model, verification) {
|
|
940
|
+
this.foldCapability(provider, model, { verification });
|
|
941
|
+
}
|
|
942
|
+
/** This pair's measured scorecard, or undefined when nothing was measured. */
|
|
943
|
+
getCapability(provider, model) {
|
|
944
|
+
return this.data.entries[entryKey(provider, model)]?.capability;
|
|
945
|
+
}
|
|
737
946
|
/**
|
|
738
947
|
* M2.2: record EXACT tokens from a provider-reported usage payload. The
|
|
739
948
|
* per-call token EMAs (α=0.3, matching latency) feed getMeasuredUsage(),
|
|
@@ -821,6 +1030,7 @@ export class ModelRegistry {
|
|
|
821
1030
|
reasoningCapability: existing?.reasoningCapability,
|
|
822
1031
|
quotaParkedUntil: Math.max(existing?.quotaParkedUntil || 0, quotaParkedUntil),
|
|
823
1032
|
source,
|
|
1033
|
+
capability: existing?.capability,
|
|
824
1034
|
lastError: reason,
|
|
825
1035
|
// A "model not found" answer is definitive for this provider × model, so
|
|
826
1036
|
// the pair is retired from every candidate pool (see `deadPair`). The flag
|
|
@@ -990,6 +1200,13 @@ export class ModelRegistry {
|
|
|
990
1200
|
if (ok) {
|
|
991
1201
|
this.markVerified(provider, model, 'telemetry', latencyMs, action, costUsd, callId);
|
|
992
1202
|
this.data.entries[key].lastUsedAt = now;
|
|
1203
|
+
// Bundle 3b: every real call is also capability evidence — how the pair held
|
|
1204
|
+
// up (robustness) and how fast it answered (performance). Folded HERE (the
|
|
1205
|
+
// single telemetry entry point) and AFTER markVerified, because markVerified
|
|
1206
|
+
// is what creates the row for a pair's first-ever call — folding earlier
|
|
1207
|
+
// would silently drop the first observation of every model. Availability is
|
|
1208
|
+
// not touched: this is evidence about the model, not a status write.
|
|
1209
|
+
this.foldCapability(provider, model, { ok, latencyMs, now });
|
|
993
1210
|
// P4 M4.4: a clean success heals mid-stream flakiness (the provider
|
|
994
1211
|
// demonstrably finishes). Decay the EMA toward 0 — never hard-reset,
|
|
995
1212
|
// so a single success doesn't erase a flaky streak. markVerified already
|
|
@@ -1055,8 +1272,41 @@ export class ModelRegistry {
|
|
|
1055
1272
|
entry.providerParked = false;
|
|
1056
1273
|
flipped = true;
|
|
1057
1274
|
}
|
|
1275
|
+
else if (errorType === 'empty-response') {
|
|
1276
|
+
// fix_model_routing P1/P2 — the provider RESOLVED but carried nothing.
|
|
1277
|
+
// This used to be recorded as a SUCCESS (the walk had no notion of a
|
|
1278
|
+
// usable response), so a model that answers every turn with an empty body
|
|
1279
|
+
// kept `verified` and `errorRate 0` and the router kept choosing it. The
|
|
1280
|
+
// error-rate bump above already sinks it in scoring; the park is the hard
|
|
1281
|
+
// stop, and it is MODEL-scoped so the provider's healthy siblings remain
|
|
1282
|
+
// routable and the pair returns automatically after the window.
|
|
1283
|
+
entry.lastError = 'empty-response (no text, no tool call)';
|
|
1284
|
+
entry.quotaParkedUntil = Math.max(entry.quotaParkedUntil, now + EMPTY_RESPONSE_PARK_MS);
|
|
1285
|
+
entry.providerParked = false;
|
|
1286
|
+
flipped = true;
|
|
1287
|
+
}
|
|
1288
|
+
else if (errorType === 'credit-exhausted') {
|
|
1289
|
+
// DEFINITIVE, and NOT a cooldown (A4). The account cannot pay, so there is
|
|
1290
|
+
// no window to wait out — a park would expire and the router would offer
|
|
1291
|
+
// the pair again, which is precisely how `openrouter/deepseek-v4.1-flash`
|
|
1292
|
+
// kept being chosen and kept answering 402. Demoted like `auth`: it takes
|
|
1293
|
+
// a funded key (or `models unblock` / a re-probe) to restore.
|
|
1294
|
+
//
|
|
1295
|
+
// This is honest about WHAT failed: the check is on the PAIR because that
|
|
1296
|
+
// is the granularity the registry stores, but the sentence names the
|
|
1297
|
+
// account, not the model — a different host may serve this same model,
|
|
1298
|
+
// and the failover walk is free to try one.
|
|
1299
|
+
entry.status = 'unavailable';
|
|
1300
|
+
entry.lastError = 'credit-exhausted (that provider account cannot pay for this call)';
|
|
1301
|
+
entry.quotaParkedUntil = 0;
|
|
1302
|
+
entry.providerParked = false;
|
|
1303
|
+
flipped = true;
|
|
1304
|
+
}
|
|
1058
1305
|
this.data.entries[key] = entry;
|
|
1059
1306
|
this.persist();
|
|
1307
|
+
// Bundle 3b — a FAILED call is capability evidence too (robustness), and the
|
|
1308
|
+
// row now exists on both paths (markVerified above, or the entry built here).
|
|
1309
|
+
this.foldCapability(provider, model, { ok, latencyMs, now });
|
|
1060
1310
|
if (flipped)
|
|
1061
1311
|
this.emitUpdated([provider], `telemetry failure (${errorType})`, 'telemetry');
|
|
1062
1312
|
if (action) {
|
|
@@ -1413,7 +1663,11 @@ export class ModelRegistry {
|
|
|
1413
1663
|
* Only call this with an AUTHORITATIVE live list (the refresh probe of a
|
|
1414
1664
|
* keyless/local runner). Entries whose model is NOT in the list are handled:
|
|
1415
1665
|
* - UNVERIFIED / UNAVAILABLE entries are DELETED entirely (never checked
|
|
1416
|
-
* again — they have no learned value worth keeping)
|
|
1666
|
+
* again — they have no learned value worth keeping), EXCEPT an entry whose
|
|
1667
|
+
* unavailability is an ACCOUNT-level refusal (`isEntitlementFailure`): that
|
|
1668
|
+
* one is the only record that this key cannot pay or is not authorized, so
|
|
1669
|
+
* deleting it loses the verdict and the next model listing re-creates the
|
|
1670
|
+
* pair as `unverified` — re-arming exactly the pin A2 exists to refuse.
|
|
1417
1671
|
* - VERIFIED entries are DEMOTED to `unavailable` with reason "model
|
|
1418
1672
|
* deleted from local system" instead of being hard-deleted — a partial
|
|
1419
1673
|
* listModels response (a model mid-pull, a gateway hiccup) must not
|
|
@@ -1433,6 +1687,14 @@ export class ModelRegistry {
|
|
|
1433
1687
|
continue;
|
|
1434
1688
|
if (e.status === 'verified')
|
|
1435
1689
|
demotedKeys.push(key);
|
|
1690
|
+
// Absence from a model list is evidence about the CATALOGUE, not about the
|
|
1691
|
+
// account. Deleting an entitlement verdict hands the pair straight back to
|
|
1692
|
+
// the next `markListed` as a fresh `unverified` entry (observed live: the
|
|
1693
|
+
// `credit-exhausted` verdict on `openrouter/deepseek/deepseek-v4.1-flash`
|
|
1694
|
+
// was gone after a refresh, so the strict pre-flight stopped firing and a
|
|
1695
|
+
// run that had refused before any call sent the request and got a raw 402).
|
|
1696
|
+
else if (isEntitlementFailure(e.lastError))
|
|
1697
|
+
continue;
|
|
1436
1698
|
else
|
|
1437
1699
|
removedKeys.push(key);
|
|
1438
1700
|
}
|
|
@@ -1491,19 +1753,38 @@ export class ModelRegistry {
|
|
|
1491
1753
|
*/
|
|
1492
1754
|
reloadFromMirror() {
|
|
1493
1755
|
this.data = this.loadMirror();
|
|
1756
|
+
this.bootSnapshot = cloneRegistryData(this.data);
|
|
1494
1757
|
}
|
|
1495
1758
|
/**
|
|
1496
1759
|
* Persist: JSON mirror synchronously (canonical, guaranteed), then mirror to
|
|
1497
1760
|
* the VectorStore namespace asynchronously (best-effort, auto-tiers to JSON
|
|
1498
1761
|
* when FAISS/native aren't installed — so it can never throw).
|
|
1499
1762
|
*/
|
|
1500
|
-
persist() {
|
|
1763
|
+
persist(opts = {}) {
|
|
1501
1764
|
this.data.updatedAt = Date.now();
|
|
1502
1765
|
const dir = memoryDir();
|
|
1766
|
+
const path = mirrorPath();
|
|
1503
1767
|
try {
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1768
|
+
mkdirSync(dir, { recursive: true });
|
|
1769
|
+
// The read-merge-write runs UNDER the lock, so two processes cannot
|
|
1770
|
+
// interleave it: the second one to run re-reads what the first wrote and
|
|
1771
|
+
// merges rather than overwriting. `writeFileAtomicSync` then makes the
|
|
1772
|
+
// publication all-or-nothing, so a concurrent READER can never parse a
|
|
1773
|
+
// half-written file (which every loader here turns into an empty state
|
|
1774
|
+
// that the next persist would write back).
|
|
1775
|
+
const merged = withFileLockSync(`${path}.lock`, () => {
|
|
1776
|
+
// A deliberate wipe (`reset`) must not adopt a peer's concurrent row —
|
|
1777
|
+
// the operator asked for an empty registry, so it is written as-is.
|
|
1778
|
+
const next = opts.replace
|
|
1779
|
+
? this.data
|
|
1780
|
+
: mergeRegistryMirror(this.bootSnapshot, this.data, this.loadMirror());
|
|
1781
|
+
writeFileAtomicSync(path, JSON.stringify(next, null, 2));
|
|
1782
|
+
return next;
|
|
1783
|
+
});
|
|
1784
|
+
// Adopt the merge, and re-baseline: this process's view is now the file's,
|
|
1785
|
+
// so the NEXT persist compares against it instead of against boot.
|
|
1786
|
+
this.data = merged.value;
|
|
1787
|
+
this.bootSnapshot = cloneRegistryData(this.data);
|
|
1507
1788
|
}
|
|
1508
1789
|
catch {
|
|
1509
1790
|
// Best-effort — a failed mirror write must never break routing.
|
|
@@ -1525,27 +1806,31 @@ export class ModelRegistry {
|
|
|
1525
1806
|
mirrorToVector(dir) {
|
|
1526
1807
|
try {
|
|
1527
1808
|
const indexPath = join(dir, 'vectors-model-registry.json');
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1809
|
+
mkdirSync(dir, { recursive: true });
|
|
1810
|
+
// Same cross-process shape as `persist()`: the read-merge-write is one
|
|
1811
|
+
// critical section, and the publication is atomic. Two processes mirroring
|
|
1812
|
+
// this namespace concurrently would otherwise drop each other's snapshot.
|
|
1813
|
+
withFileLockSync(`${indexPath}.lock`, () => {
|
|
1814
|
+
let entries = {};
|
|
1815
|
+
try {
|
|
1816
|
+
if (existsSync(indexPath)) {
|
|
1817
|
+
const raw = JSON.parse(readFileSync(indexPath, 'utf-8'));
|
|
1818
|
+
if (raw && typeof raw === 'object' && raw.entries && typeof raw.entries === 'object') {
|
|
1819
|
+
entries = raw.entries;
|
|
1820
|
+
}
|
|
1536
1821
|
}
|
|
1537
1822
|
}
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1823
|
+
catch {
|
|
1824
|
+
// Corrupt/missing file — start from an empty index.
|
|
1825
|
+
}
|
|
1826
|
+
entries[VECTOR_SNAPSHOT_ID] = {
|
|
1827
|
+
id: VECTOR_SNAPSHOT_ID,
|
|
1828
|
+
vector: [1], // 1-dim placeholder — we never search, only store.
|
|
1829
|
+
metadata: { snapshot: this.data },
|
|
1830
|
+
createdAt: Date.now(),
|
|
1831
|
+
};
|
|
1832
|
+
writeFileAtomicSync(indexPath, JSON.stringify({ entries, version: 2 }, null, 2));
|
|
1833
|
+
});
|
|
1549
1834
|
this.vectorMirrored = true;
|
|
1550
1835
|
}
|
|
1551
1836
|
catch {
|
|
@@ -1684,7 +1969,8 @@ export class ModelRegistry {
|
|
|
1684
1969
|
/** Clear the registry (CLI / tests). */
|
|
1685
1970
|
reset() {
|
|
1686
1971
|
this.data = emptyState();
|
|
1687
|
-
|
|
1972
|
+
// Also overwrites the vector snapshot with the empty state.
|
|
1973
|
+
this.persist({ replace: true });
|
|
1688
1974
|
}
|
|
1689
1975
|
}
|
|
1690
1976
|
// ─── Singleton ──────────────────────────────────────────────────────────────
|