fix(ci): clear base-reds on release/v3.8.50 (migration collision + 4 masked gates) (#9600)

Validated in local merge-train (diegosouzapw batch)
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-08-06 10:40:56 -03:00
committed by GitHub
parent ba0a0751c4
commit f2e36ad0ce
19 changed files with 330 additions and 53 deletions

View File

@@ -524,7 +524,7 @@
"_rebaseline_pr1043_minimax_tts": "Upstream port decolua/9router#1043 (toanalien) own growth: audioSpeech.ts 965->1061 (+96). Adds MiniMax T2A v2 TTS dispatch (handleMinimaxSpeech + hexToBytes helper) — provider entry was already in audioRegistry (format: minimax-tts) but no handler existed, falling through to the OpenAI-compatible default that fails (T2A has custom shape + hex-encoded audio + base_resp envelope). New branch sits next to the other inline provider branches (xiaomi-mimo, coqui, tortoise, aws-polly) — extracting would just create indirection. Covered by tests/unit/minimax-tts-1043.test.ts (3 tests, GREEN: success, base_resp error, invalid-hex).",
"_rebaseline_pr4592_exclude_exhausted_auto": "Reconcile #4592 already-merged growth: combo.ts 2991->3036 (+45, terminal-status quota-cutoff exclusion in buildAutoCandidates + opt-in gate). Fast-gate PR->release does not run check:file-size.",
"open-sse/executors/antigravity.ts": 1528,
"open-sse/executors/base.ts": 1635,
"open-sse/executors/base.ts": 1640,
"open-sse/executors/chatgpt-web.ts": 3241,
"open-sse/executors/codex.ts": 1562,
"open-sse/executors/cursor.ts": 1560,
@@ -539,7 +539,7 @@
"open-sse/mcp-server/schemas/tools.ts": 1505,
"open-sse/mcp-server/server.ts": 1411,
"open-sse/mcp-server/tools/advancedTools.ts": 1120,
"open-sse/services/accountFallback.ts": 1966,
"open-sse/services/accountFallback.ts": 1972,
"open-sse/services/adobeFireflyClient.ts": 2385,
"open-sse/services/claudeCodeCompatible.ts": 1202,
"open-sse/services/combo.ts": 3648,
@@ -558,7 +558,7 @@
"src/app/(dashboard)/dashboard/endpoint/EndpointPageClient.tsx": 2615,
"src/app/(dashboard)/dashboard/health/page.tsx": 1165,
"src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditConnectionModal.tsx": 1316,
"src/app/(dashboard)/dashboard/providers/page.tsx": 1928,
"src/app/(dashboard)/dashboard/providers/page.tsx": 1944,
"src/app/(dashboard)/dashboard/runtime/RuntimePageClient.tsx": 1201,
"src/app/(dashboard)/dashboard/settings/components/PricingTab.tsx": 1019,
"src/app/(dashboard)/dashboard/settings/components/ProxyRegistryManager.tsx": 1464,
@@ -573,7 +573,7 @@
"src/lib/tokenHealthCheck.ts": 1021,
"src/lib/db/apiKeys.ts": 1529,
"src/lib/db/core.ts": 1637,
"src/lib/db/migrationRunner.ts": 1077,
"src/lib/db/migrationRunner.ts": 1084,
"src/lib/db/models.ts": 1097,
"src/lib/db/providers.ts": 1034,
"src/lib/memory/retrieval.ts": 1073,
@@ -605,5 +605,6 @@
"_rebaseline_2026_08_02_v3850_agentrouter_responses": "Release v3.8.50 AgentRouter/Codex compatibility reconciliation. open-sse/executors/base.ts 1562->1578: #9190 wires AgentRouter's selected Claude/OpenAI/Responses protocol through the existing executor URL, auth, identity-header and fingerprint chokepoints; the reusable alternate resolver remains outside base.ts. open-sse/utils/stream.ts 2887->2889: #9213 evaluates Responses ID and usage normalization independently so response.completed always receives finite usage.total_tokens instead of short-circuiting after an ID rewrite. tests/unit/chatcore-translation-paths.test.ts 2769->2776: #9191 updates the existing Claude-Code bridge assertions for the dynamic AgentRouter wire image. PR #9224 offsets its own chatCore growth by extracting the AgentRouter protocol decisions into chatCore/agentRouterProtocol.ts, leaving chatCore below its frozen ceiling. Covered by agentrouter executor/chatCore protocol tests, chatcore translation-path tests, and responses-commentary-passthrough tests.",
"_rebaseline_2026_08_05_9323_agentrouter_waf_retry": "PR #9323 (fix(agentrouter): retry on 400 content-blocked + burst guard) own growth: open-sse/executors/base.ts 1578->1623 (check-file-size.mjs conta via split(\"\\n\").length; wc -l ve 1622). As +45 linhas sao o WAF_RETRY_CONFIG + o burst guard via gateOutboundRequest() para o WAF do agentrouter.org, com comentarios explicando o porque de cada mitigacao e cobertos por tests/unit/base-executor-waf-retry.test.ts e tests/unit/wafRateLimit.test.ts. Crescimento funcional legitimo, nao inchaco.",
"_rebaseline_2026_08_05_9529_own_growth": "PR #9529 own growth (base release/v3.8.50 medida EXATAMENTE nos frozen antigos, entao o modo base-relative #8522 nao cobre): open-sse/services/rateLimitManager.ts 1060->1105 (+45: helper applyLimiterSettings() que re-arma o heartbeat do reservoir apos updateSettings — fix do bug Bottleneck 2.19.5 que congelava a fila weighted; TDD em tests/unit/ratelimit-reservoir-refresh.test.ts); tests/integration/chat-pipeline.test.ts 1592->1598 (+6: User-Agent do codex derivado de getCodexClientVersion() em vez de literal pinado — teste-irmao alinhado ao contrato); tests/unit/provider-validation-specialty.test.ts 2980->2985 (+5: cobertura NOVA claude-web 429 -> valid:false, alinhamento #9406); open-sse/translator/response/openai-responses.ts 1174->1204 (+30: buildResponsesReasoningSummaryDelta MOVIDA do leaf pureHelpers.ts para o host — a funcao do #9500 muta stream state e violava o contrato do leaf puro; o LOC total do par host+leaf nao cresceu, o pureHelpers encolheu o mesmo tanto). Crescimento por fix de producao + cobertura adicional + realocacao arquitetural, nao inchaco.",
"_rebaseline_2026_08_06_v3850_inherited_drift_reconcile": "Reconciliacao 2026-08-06 do drift ACUMULADO da release/v3.8.50 apos o lote de merges de 08-05/06: 13 arquivos acima do frozen no tip puro 8180b49ce1 (medidos pelo proprio gate). O modo PR base-relative (#8522) deixa PRs inocentes passarem, e os rebaselines individuais dos PRs se perderam nas resolucoes sucessivas de conflito deste hot-file — o drift so aparece no modo absoluto (nightly/local). Crescimentos funcionais dos PRs mergeados: #9024 topology click-nav src/app/(dashboard)/dashboard/HomePageClient.tsx; #9324 OpenRouter enrich src/app/(dashboard)/dashboard/providers/page.tsx; #9329 quota card ordering src/app/(dashboard)/dashboard/usage/components/ProviderLimits/index.tsx; #9193 context-window suffixes src/sse/handlers/chat.ts; #9332 nested Claude server tool ids open-sse/executors/base.ts; #9228 strip orphaned tool outputs open-sse/executors/codex.ts; #9236 nvidia tool-name normalize open-sse/executors/default.ts; #9314 nested tool_call validation open-sse/executors/kiro.ts; #9260 caller identity REST hops open-sse/mcp-server/server.ts; #8934 cache breakpoints tests tests/unit/chatcore-translation-paths.test.ts; #9193 suffix tests tests/unit/combo-routing-engine.test.ts; #9196 reasoning-on-tool-finish tests tests/unit/sse-auth.test.ts; #9163 GPT-5.6 Max reasoning tests tests/unit/translator-openai-to-kiro.test.ts. default.ts e kiro.ts entram no frozen (estavam sem entrada, acima do cap 1000). Atualizacao pos-medicao (a base avancou durante o ciclo do PR): src/sse/handlers/chat.ts 1857->1877 (#9184 affinity EOF evict) e open-sse/executors/default.ts 1027->1042 (#9005 Kimi K3 tool-name backfill)."
"_rebaseline_2026_08_06_v3850_inherited_drift_reconcile": "Reconciliacao 2026-08-06 do drift ACUMULADO da release/v3.8.50 apos o lote de merges de 08-05/06: 13 arquivos acima do frozen no tip puro 8180b49ce1 (medidos pelo proprio gate). O modo PR base-relative (#8522) deixa PRs inocentes passarem, e os rebaselines individuais dos PRs se perderam nas resolucoes sucessivas de conflito deste hot-file — o drift so aparece no modo absoluto (nightly/local). Crescimentos funcionais dos PRs mergeados: #9024 topology click-nav src/app/(dashboard)/dashboard/HomePageClient.tsx; #9324 OpenRouter enrich src/app/(dashboard)/dashboard/providers/page.tsx; #9329 quota card ordering src/app/(dashboard)/dashboard/usage/components/ProviderLimits/index.tsx; #9193 context-window suffixes src/sse/handlers/chat.ts; #9332 nested Claude server tool ids open-sse/executors/base.ts; #9228 strip orphaned tool outputs open-sse/executors/codex.ts; #9236 nvidia tool-name normalize open-sse/executors/default.ts; #9314 nested tool_call validation open-sse/executors/kiro.ts; #9260 caller identity REST hops open-sse/mcp-server/server.ts; #8934 cache breakpoints tests tests/unit/chatcore-translation-paths.test.ts; #9193 suffix tests tests/unit/combo-routing-engine.test.ts; #9196 reasoning-on-tool-finish tests tests/unit/sse-auth.test.ts; #9163 GPT-5.6 Max reasoning tests tests/unit/translator-openai-to-kiro.test.ts. default.ts e kiro.ts entram no frozen (estavam sem entrada, acima do cap 1000). Atualizacao pos-medicao (a base avancou durante o ciclo do PR): src/sse/handlers/chat.ts 1857->1877 (#9184 affinity EOF evict) e open-sse/executors/default.ts 1027->1042 (#9005 Kimi K3 tool-name backfill).",
"_rebaseline_2026_08_06b_v3850_sweepreds_drift": "Segunda reconciliacao de 2026-08-06 (/sweep-reds sobre o tip puro 2ddbbc61a6): 3 arquivos voltaram a passar do frozen apos os merges do mesmo dia, com atribuicao 1:1 por commit. (1) src/app/(dashboard)/dashboard/providers/page.tsx 1928->1944 e (2) open-sse/executors/base.ts 1635->1640, ambos do #9515 (feat(radar): flag-gated signed free-model catalog overlay, commit e7f6b1d130) — o overlay do Radar entra por wiring nos chokepoints ja existentes (a resolucao/verificacao do catalogo assinado mora fora destes dois arquivos); +16 e +5 linhas liquidas nao sao extraiveis sem inventar um leaf por callsite. (3) open-sse/services/accountFallback.ts 1966->1972 do #8704 (commit c4527f97bd), +6 linhas de dados em CREDITS_EXHAUSTED_SIGNALS ('has been exhausted', fixes #8631). src/sse/handlers/chat.ts 1880>1877 tambem estava violando e NAO entra aqui de proposito: e drenado por encolhimento na PR #9598, sem rebaseline. Crescimento proprio DESTA PR: src/lib/db/migrationRunner.ts 1077->1084 (+7) — o guard retroativo em isSchemaAlreadyApplied para os arquivos renumerados 137/138, exigido pela propria mensagem de erro de colisao do runner (ambas as migracoes sao ALTER TABLE ADD COLUMN puro, nao idempotente). Dois `case` + dois `return hasColumn(...)` + 3 linhas de comentario dentro do switch existente; nao extraivel."
}

View File

@@ -82,9 +82,10 @@
"tightenSlack": 10
},
"openapiCoverage.pct": {
"value": 38,
"value": 39.2,
"direction": "up",
"eps": 0.5,
"_tighten_2026_08_06_v3850_sweepreds": "38.0 -> 39.2 (aperto EXIGIDO pelo step 'Require-tighten (blocking)', que estava vermelho em ~60 PRs abertas de release/v3.8.50 — base-red herdado, nao defeito das PRs). A cobertura melhorou no ciclo porque as rotas novas entraram documentadas. 39.2 = valor medido pelo CI Quality Ratchet no run 31088889488; o tip puro 2ddbbc61a6 mede 39.3 localmente (npm run check:openapi-coverage: 247/628 rotas), entao 39.2 e o valor conservador dos dois. Aperto = gate mais ESTRITO, nunca mascaramento.",
"_tighten_2026_07_04_v3844_release": "36.9 -> 39.3 (aperto exigido pelo --require-tighten no PR de release #5925). A cobertura OpenAPI melhorou no ciclo (9 rotas documentadas em 8fb020676 + as rotas novas de #5939/#5817/#6034/#5998 documentadas junto das features). 39.3 = valor medido pelo CI Quality Ratchet no run 28708141003 (tip 00c55afcb).",
"_rebaseline_2026_06_28_v3839_release": "37.8 -> 36.9 (-0.9, beyond the 0.5 eps). v3.8.39 cycle drift surfaced ONLY on the release PR (the openapi-coverage ratchet does NOT run on PR->release fast-gates). The cycle added API/internal routes (antigravity paste-credentials onboarding, CCR ranged/grep/stats retrieve params, mcp 404 session handling) faster than docs/openapi.yaml coverage; documenting LOCAL_ONLY/internal onboarding routes in the PUBLIC spec would be gaming (same precedent as _rebaseline_2026_06_18_v3828_cycle_close). Measured by CI collect-metrics (run 28317145160) = 36.9. My release-finalize tree touches no routes (only the openapi.yaml version bump). Raising coverage by documenting public routes is tracked as follow-up doc debt.",
"_rebaseline_2026_06_23_v3834_release": "38.4 -> 37.8 (-0.6, beyond the 0.5 eps so it failed the ratchet). v3.8.34 cycle drift: contributor PRs added API routes (e.g. quota/usage/opencode-go endpoints) faster than openapi.yaml coverage; the openapi-coverage ratchet does NOT run on PR->release fast-gates so it surfaced only on the release PR. Verified my release-finalize working tree touches no routes / openapi paths (only version bump in openapi.yaml). Measured by CI quality:collect (run 28000387577) = 37.8. Raising coverage by documenting the new routes is tracked as follow-up doc debt.",

View File

@@ -223,6 +223,7 @@ import { recordCost } from "@/domain/costRules";
import { calculateCost } from "@/lib/usage/costCalculator";
import {
buildClaudePassthroughToolNameMap,
mergeResponseToolNameMap,
normalizeOpenAIToolFinishReasons,
restoreNonStreamingToolNames,
} from "./chatCore/passthroughToolNames.ts";

View File

@@ -192,12 +192,17 @@ describe("TierResolver", () => {
});
it("uses cache for repeated models", () => {
classifyTiers([
clearTierCache();
const results = classifyTiers([
{ provider: "openai", model: "gpt-4o" },
{ provider: "openai", model: "gpt-4o" },
]);
// If cache works, second call should be instant; test passes if no error
expect(true).toBe(true);
// Observable effect of the cache: the duplicate resolves to the same tier and only
// ONE entry is memoized (getTierStats counts cache entries, not classify calls).
assert.equal(results.length, 2);
assert.equal(results[0].tier, results[1].tier);
const stats = getTierStats();
assert.equal(stats.free + stats.cheap + stats.premium, 1);
});
});

View File

@@ -21,6 +21,7 @@ import { handlePipelineChat, type PipelineStep } from "../pipeline.ts";
import type { resolveComboSetupConfig } from "../comboConfig.ts";
import { clampComboDepth, MAX_GLOBAL_ATTEMPTS, resolveDelayMs } from "./comboPredicates.ts";
import { resolveComboRuntimeUnits, resolveComboTargets } from "./comboStructure.ts";
import { isComboModelVisible } from "./comboVisibility.ts";
import { buildFusionHandleSingleModel, extractFusionPanelSpec } from "./fusionPanel.ts";
import {
clampStickyWeightedTargetLimit,
@@ -340,12 +341,20 @@ export async function tryFusionDispatch(args: {
runCombo: RunCombo;
}): Promise<Response | null> {
const { cfg, combo, config, strategy, log } = args;
const judgeModel = typeof cfg.judgeModel === "string" ? cfg.judgeModel : undefined;
const configuredJudge = typeof cfg.judgeModel === "string" ? cfg.judgeModel : undefined;
// The panel is filtered for hidden models by resolveComboTargets, but the
// explicit judge is a bare string that never passes through it (#8878). Drop a
// hidden judge so fusion falls back to a surviving panel member instead of
// dispatching a model the operator hid.
const judgeModel =
configuredJudge && !isComboModelVisible(configuredJudge, null, args.hiddenModelsByProvider)
? undefined
: configuredJudge;
const fusionTuning =
cfg.fusionTuning && typeof cfg.fusionTuning === "object"
? (cfg.fusionTuning as FusionTuning)
: undefined;
if (strategy !== "fusion" && (judgeModel || fusionTuning)) {
if (strategy !== "fusion" && (configuredJudge || fusionTuning)) {
log.warn(
"COMBO",
`Combo "${combo.name}" sets config.judgeModel/fusionTuning but strategy is "${strategy}" — these fields are only consumed by the fusion strategy and will be ignored (#6455)`
@@ -353,16 +362,30 @@ export async function tryFusionDispatch(args: {
}
if (strategy !== "fusion") return null;
const { panel: fusionModels, comboRefUnits } = extractFusionPanelSpec(
resolveComboTargets(
combo,
args.allCombos,
clampComboDepth(config.maxComboDepth),
args.hiddenModelsByProvider
).map((target) => target.modelStr),
const resolvedFusionTargets = resolveComboTargets(
combo,
args.allCombos,
clampComboDepth(config.maxComboDepth),
args.hiddenModelsByProvider
);
// extractFusionPanelSpec only understands model strings / combo refs, so the
// resolved targets have to be flattened before it runs. Keep them indexed so
// the panel can be rehydrated below — dispatching the bare strings strips
// `providerId` and every panel member loses its provider identity (#8878).
const resolvedByModelStr = new Map<string, (typeof resolvedFusionTargets)[number]>();
for (const target of resolvedFusionTargets) {
if (!resolvedByModelStr.has(target.modelStr)) resolvedByModelStr.set(target.modelStr, target);
}
const { panel: fusionPanel, comboRefUnits } = extractFusionPanelSpec(
resolvedFusionTargets.map((target) => target.modelStr),
combo.name,
null
);
// A panel entry naming a combo ref stays a string (it is a combo name, not a
// model); everything else regains its resolved target.
const fusionModels = fusionPanel.map((entry) =>
comboRefUnits.has(entry) ? entry : (resolvedByModelStr.get(entry) ?? entry)
);
// Untyped like the existing `nestingContext` further down — `nesting` is
// already `ComboNestingContext | null` per HandleComboChatOptions, no new
// import needed.

View File

@@ -126,6 +126,9 @@ export const PACK_ARTIFACT_ROOT_ALLOWED_EXACT_PATHS: string[] = [
// #7802: imported by scripts/build/postinstall.mjs to repair tls-client-node's
// native binary (chatgpt-web/claude-web/grok-web/lmarena/perplexity-web transport).
"scripts/build/fixTlsClientNodeBinary.mjs",
// #8859: imported by scripts/build/postinstall.mjs to repair playwright-core's
// browser resolution on Termux/Android (no glibc, no bundled browsers).
"scripts/build/fixPlaywrightAndroid.mjs",
// #5227: imported at runtime by bin/cli/commands/serve.mjs (heap auto-calibration).
"scripts/build/runtime-env.mjs",
"scripts/build/sync-env.mjs",

View File

@@ -116,6 +116,19 @@ curl -X POST https://localhost:20128/api/services/9router/auto-start \
-d '{}'
```
### POST /api/services/9router/auto-restart-adopted
Toggle 9Router auto-restart-when-adopted
When enabled, an externally-adopted (not OmniRoute-spawned) 9Router process is restarted under OmniRoute's own supervisor on the next health-check cycle instead of being left as adopted-only. **LOCAL_ONLY** — loopback only.
```bash
curl -X POST https://localhost:20128/api/services/9router/auto-restart-adopted \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### POST /api/services/cliproxy/install
Install CLIProxyAPI from npm
@@ -205,6 +218,19 @@ curl -X POST https://localhost:20128/api/services/cliproxy/auto-start \
-d '{}'
```
### POST /api/services/cliproxy/auto-restart-adopted
Toggle CLIProxyAPI auto-restart-when-adopted
When enabled, an externally-adopted (not OmniRoute-spawned) CLIProxyAPI process is restarted under OmniRoute's own supervisor on the next health-check cycle instead of being left as adopted-only. **LOCAL_ONLY** — loopback only.
```bash
curl -X POST https://localhost:20128/api/services/cliproxy/auto-restart-adopted \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### POST /api/services/mux/install
Install Mux from npm
@@ -294,6 +320,19 @@ curl -X POST https://localhost:20128/api/services/mux/auto-start \
-d '{}'
```
### POST /api/services/mux/auto-restart-adopted
Toggle Mux auto-restart-when-adopted
When enabled, an externally-adopted (not OmniRoute-spawned) Mux process is restarted under OmniRoute's own supervisor on the next health-check cycle instead of being left as adopted-only. **LOCAL_ONLY** — loopback only.
```bash
curl -X POST https://localhost:20128/api/services/mux/auto-restart-adopted \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### POST /api/services/bifrost/install
Install Bifrost
@@ -383,6 +422,193 @@ curl -X POST https://localhost:20128/api/services/bifrost/auto-start \
-d '{}'
```
### POST /api/services/bifrost/auto-restart-adopted
Toggle Bifrost auto-restart-when-adopted
When enabled, an externally-adopted (not OmniRoute-spawned) Bifrost process is restarted under OmniRoute's own supervisor on the next health-check cycle instead of being left as adopted-only. **LOCAL_ONLY** — loopback only.
```bash
curl -X POST https://localhost:20128/api/services/bifrost/auto-restart-adopted \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### POST /api/services/dario/install
Install Dario from npm
Installs the `@askalf/dario` npm package (Claude-account-pool proxy) under DATA_DIR/services/dario/. Uses execFile (no shell interpolation — hard rule #13). **LOCAL_ONLY** — loopback only.
```bash
curl -X POST https://localhost:20128/api/services/dario/install \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### POST /api/services/dario/start
Start Dario
Spawns the Dario process. Idempotent if already running. **LOCAL_ONLY** — loopback only.
```bash
curl -X POST https://localhost:20128/api/services/dario/start \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### POST /api/services/dario/stop
Stop Dario
Gracefully stops Dario. Idempotent — returns a stopped status even if no supervisor is currently tracking the process. **LOCAL_ONLY** — loopback only.
```bash
curl -X POST https://localhost:20128/api/services/dario/stop \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### POST /api/services/dario/restart
Restart Dario
Equivalent to stop() then start() under the operation lock. **LOCAL_ONLY** — loopback only.
```bash
curl -X POST https://localhost:20128/api/services/dario/restart \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### POST /api/services/dario/update
Update Dario to a newer npm version
Stops the service (if running), installs the newer npm version, then restarts it if it was running before the update. **LOCAL_ONLY** — loopback only.
```bash
curl -X POST https://localhost:20128/api/services/dario/update \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### GET /api/services/dario/status
Get Dario status
Returns combined live supervisor state and DB metadata, including the auto-start / auto-restart-adopted flags and whether an update is available. **LOCAL_ONLY** — loopback only.
```bash
curl https://localhost:20128/api/services/dario/status \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### POST /api/services/dario/auto-start
Toggle Dario auto-start
When enabled, Dario starts automatically on the next OmniRoute boot. **LOCAL_ONLY** — loopback only.
```bash
curl -X POST https://localhost:20128/api/services/dario/auto-start \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### POST /api/services/dario/auto-restart-adopted
Toggle Dario auto-restart-when-adopted
When enabled, an externally-adopted (not OmniRoute-spawned) Dario process is restarted under OmniRoute's own supervisor on the next health-check cycle instead of being left as adopted-only. **LOCAL_ONLY** — loopback only.
```bash
curl -X POST https://localhost:20128/api/services/dario/auto-restart-adopted \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### POST /api/services/dario/admin/login-start
Start a Dario account-pool login (device-code style)
Forwards to the running Dario instance's `POST /admin/login/start` using the stored admin token. The operator opens the returned `authorize_url`, approves in their own Claude account, then posts the displayed code to `/admin/login-complete`. **LOCAL_ONLY** — loopback only.
```bash
curl -X POST https://localhost:20128/api/services/dario/admin/login-start \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### POST /api/services/dario/admin/login-complete
Complete a Dario account-pool login
Forwards to the running Dario instance's `POST /admin/login/complete`. On success the account becomes routable immediately (Dario hot-reloads its pool). **LOCAL_ONLY** — loopback only.
```bash
curl -X POST https://localhost:20128/api/services/dario/admin/login-complete \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### GET /api/services/dario/admin/accounts
List Dario account-pool accounts
Forwards to the running Dario instance's `GET /admin/accounts`. **LOCAL_ONLY** — loopback only.
```bash
curl https://localhost:20128/api/services/dario/admin/accounts \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### DELETE /api/services/dario/admin/accounts
Remove a Dario account-pool account
Forwards to the running Dario instance's `DELETE /admin/accounts/<alias>`. The alias is taken from a `?alias=` query param or a `{ alias }` JSON body. **LOCAL_ONLY** — loopback only.
```bash
curl -X DELETE https://localhost:20128/api/services/dario/admin/accounts \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### GET /api/services/dario/admin/import-from-omniroute
List OmniRoute claude connections eligible for Dario import
Returns eligible OmniRoute `claude` OAuth provider connections (metadata only — id/name/email/org tier, never tokens) so the UI can offer a picker when more than one exists. **LOCAL_ONLY** — loopback only.
```bash
curl https://localhost:20128/api/services/dario/admin/import-from-omniroute \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
```
### POST /api/services/dario/admin/import-from-omniroute
Import an OmniRoute claude connection's OAuth tokens into Dario
Writes the source connection's access/refresh token pair directly into Dario's own account-file store (`~/.dario/accounts/<alias>.json`), reusing the shared Claude Code OAuth client_id, then restarts the Dario supervisor so it picks up the new account. **LOCAL_ONLY** — loopback only.
```bash
curl -X POST https://localhost:20128/api/services/dario/admin/import-from-omniroute \
-H "Authorization: Bearer $OMNIROUTE_TOKEN"
-H "Content-Type: application/json" \
-d '{}'
```
### GET /api/services/{name}/logs
Stream service logs via SSE

View File

@@ -11537,7 +11537,10 @@
"noSavedProxiesError": "No saved proxies found. Add proxies in Settings → Proxy first.",
"updateProviderFailed": "Failed to update provider",
"providerEnabled": "{provider} enabled",
"providerDisabled": "{provider} disabled"
"providerDisabled": "{provider} disabled",
"providerAdded": "{provider} added",
"add": "Add",
"manualApiKey": "Use a manual API key"
},
"gamification": {
"leaderboardScopes": {

View File

@@ -5963,7 +5963,8 @@
"kimi-coding": "Conecte o Kimi Coding com o fluxo OAuth existente.",
"kiro": "Nível gratuito: 50 créditos/mês (~25K100K tokens). ⚠️ Os Termos de Serviço (ToS) do Kiro proíbem o uso de proxy/harness de terceiros.",
"codex": "Conecte o OpenAI Codex com o fluxo OAuth existente.",
"qwen": "Conecte o Qwen Code com o fluxo OAuth existente."
"qwen": "Conecte o Qwen Code com o fluxo OAuth existente.",
"unorouter": "Crie uma chave de API em https://unorouter.ai e cole aqui como Bearer token."
},
"passthroughModelsDescription": "{provider} aceita IDs de modelo nativos do provedor. Importe de /models ou adicione IDs personalizados para roteamento.",
"bedrockModelsDescription": "Os modelos Amazon Bedrock têm escopo definido por região da AWS. Importe de /models ou adicione IDs de modelo Bedrock habilitados na região selecionada.",
@@ -10858,7 +10859,9 @@
"goNow": "Ir agora",
"message": "O MITM Proxy agora vive no AgentBridge.",
"title": "Esta página foi movida"
}
},
"noMappingsDesc": "Nenhum mapeamento de modelo configurado ainda. Adicione mapeamentos para rotear as requisições do agente pelo OmniRoute.",
"addMapping": "Adicionar mapeamento"
},
"providerStats": {
"unknownError": "Erro desconhecido",
@@ -11534,7 +11537,10 @@
"noSavedProxiesError": "Nenhum proxy salvo encontrado. Adicione proxies em Configurações → Proxy primeiro.",
"updateProviderFailed": "Falha ao atualizar provedor",
"providerEnabled": "{provider} ativado",
"providerDisabled": "{provider} desativado"
"providerDisabled": "{provider} desativado",
"providerAdded": "{provider} adicionado",
"add": "Adicionar",
"manualApiKey": "Usar uma chave de API manual"
},
"gamification": {
"leaderboardScopes": {

View File

@@ -5955,7 +5955,8 @@
"kimi-coding": "Kết nối Kimi Coding bằng luồng OAuth hiện có.",
"kiro": "Gói miễn phí: 50 tín dụng/tháng (khoảng 25100 nghìn token). ⚠️ Điều khoản Kiro cấm sử dụng proxy/harness của bên thứ ba.",
"codex": "Kết nối OpenAI Codex bằng luồng OAuth hiện có.",
"qwen": "Kết nối Qwen Code bằng luồng OAuth hiện có."
"qwen": "Kết nối Qwen Code bằng luồng OAuth hiện có.",
"unorouter": "Tạo khóa API tại https://unorouter.ai, sau đó dán vào đây dưới dạng Bearer token."
},
"passthroughModelsDescription": "{provider} chấp nhận ID mô hình gốc của nhà cung cấp. Nhập từ /models hoặc thêm ID tùy chỉnh để định tuyến.",
"bedrockModelsDescription": "Các mô hình Amazon Bedrock được giới hạn theo vùng AWS. Nhập từ /models hoặc thêm ID mô hình Bedrock được bật trong vùng đã chọn.",
@@ -10850,7 +10851,9 @@
"goNow": "Đi ngay",
"message": "MITM Proxy hiện nằm trong AgentBridge.",
"title": "Trang này đã được di chuyển"
}
},
"noMappingsDesc": "Chưa cấu hình ánh xạ mô hình nào. Hãy thêm ánh xạ để định tuyến yêu cầu của agent qua OmniRoute.",
"addMapping": "Thêm ánh xạ"
},
"providerStats": {
"unknownError": "Lỗi không xác định",
@@ -11526,7 +11529,10 @@
"noSavedProxiesError": "Không tìm thấy proxy đã lưu. Hãy thêm proxy trong Cài đặt → Proxy trước.",
"updateProviderFailed": "Không thể cập nhật nhà cung cấp",
"providerEnabled": "Đã bật {provider}",
"providerDisabled": "Đã tắt {provider}"
"providerDisabled": "Đã tắt {provider}",
"providerAdded": "Đã thêm {provider}",
"add": "Thêm",
"manualApiKey": "Dùng khóa API thủ công"
},
"gamification": {
"leaderboardScopes": {

View File

@@ -465,6 +465,13 @@ function isSchemaAlreadyApplied(
// exists the rebuild ran — skip re-executing the rename/copy/drop, which
// would fail on the missing proxy_assignments_pre117 table.
return hasColumn(db, "proxy_assignments", "position");
// Retroactive guard for the 135/136 renumber (#8523 landed onto slots already taken
// by #8908/#9515): a DB that ran these under the old numbers already has the column,
// and a bare ALTER TABLE ADD COLUMN would throw on the re-run under the new number.
case "137":
return hasColumn(db, "version_manager", "auto_restart_adopted");
case "138":
return hasColumn(db, "upstream_proxy_config", "fallback_backend");
default:
return false;
}

View File

@@ -1,4 +1,4 @@
-- Migration 135: Auto-restart-adopted toggle for embedded services
-- Migration 137: Auto-restart-adopted toggle for embedded services
--
-- When a supervisor's probeBeforeSpawn finds a healthy instance already
-- listening on its port, it adopts that process instead of spawning a new

View File

@@ -1,4 +1,4 @@
-- Migration 136: Dario failover backend selection for upstream_proxy_config
-- Migration 138: Dario failover backend selection for upstream_proxy_config
--
-- Adds `fallback_backend` so a provider whose mode is 'fallback' can choose
-- WHICH embedded proxy handles the retry leg — CLIProxyAPI (the historical,

View File

@@ -168,19 +168,6 @@ export async function update(): Promise<InstallResult> {
return install("latest");
}
export async function uninstall(): Promise<void> {
const nmDir = path.join(getDarioInstallDir(), "node_modules");
if (fs.existsSync(nmDir)) {
fs.rmSync(nmDir, { recursive: true, force: true });
}
await upsertVersionManagerTool({
tool: "dario",
status: "not_installed",
installedVersion: null,
binaryPath: null,
});
}
/**
* Build spawn args for ServiceSupervisor.start().
*

View File

@@ -30,13 +30,13 @@ const {
isControlPlaneProxyDirectFallbackEnabled,
} = await import("../../src/shared/utils/featureFlags.ts");
const EXPECTED_FEATURE_FLAG_COUNT = 44;
const EXPECTED_FEATURE_FLAG_COUNT = 45;
// ──────────────────────────────────────────────────────
// Test group 1 — Flag definitions registry
// ──────────────────────────────────────────────────────
describe("featureFlagDefinitions", () => {
it("has exactly 44 flag definitions", () => {
it("has exactly 45 flag definitions", () => {
assert.strictEqual(FEATURE_FLAG_DEFINITIONS.length, EXPECTED_FEATURE_FLAG_COUNT);
});
@@ -332,7 +332,7 @@ describe("resolveFeatureFlag", () => {
});
describe("resolveAllFeatureFlags", () => {
it("returns all 44 flags", () => {
it("returns all 45 flags", () => {
const all = resolveAllFeatureFlags();
assert.strictEqual(all.length, EXPECTED_FEATURE_FLAG_COUNT);
});

View File

@@ -1,7 +1,7 @@
// Characterization of the providers.ts catalog split (god-file decomposition): the host became a
// barrel that re-exports 10 data catalogs now living under constants/providers/*, and APIKEY is
// merged from 6 semantic family files (apikey/<family>.ts). Locks: the public surface (every catalog
// + helpers still exported), the spread-merge integrity (196 APIKEY entries, no loss/dup), and that
// + helpers still exported), the spread-merge integrity (197 APIKEY entries, no loss/dup), and that
// load-time Zod validation still runs. Pure-data move → behavior must be identical.
// Count was 171 before obsolete provider removals (PR #6675: glhf/kluster/cablyai/inclusionai etc.,
// 171->167) plus #6126 (ClinePass dual-auth): the API-key-only APIKEY_PROVIDERS_GATEWAYS entry was
@@ -17,7 +17,7 @@
// sarvam+plamo in regional) to 193, then #8170 (inception/typhoon — inception in frontier-labs,
// typhoon in regional) to 195, then Firecrawl dual search+fetch under SEARCH_PROVIDERS.firecrawl
// (removed specialty-media duplicate) to 194, #8861 (Xiaomi MiMo Token Plan, regional) to 195, and
// the Cheaper Inference gateway (OSS-sponsor reseller, gateways family) to 196.
// the Cheaper Inference gateway (OSS-sponsor reseller, gateways family) to 197 (UnoRouter, #9009).
import { test } from "node:test";
import assert from "node:assert/strict";
@@ -46,12 +46,12 @@ test("barrel still exports every catalog + key helpers", () => {
}
});
test("APIKEY_PROVIDERS merges the 6 family files into 196 entries (no loss / no dup)", async () => {
test("APIKEY_PROVIDERS merges the 6 family files into 197 entries (no loss / no dup)", async () => {
const keys = Object.keys((P as Record<string, object>).APIKEY_PROVIDERS);
assert.equal(keys.length, 196);
assert.equal(new Set(keys).size, 196, "duplicate keys after spread-merge");
assert.equal(keys.length, 197);
assert.equal(new Set(keys).size, 197, "duplicate keys after spread-merge");
// the merged object's entry-count equals the sum of the 6 semantic family files; families are a
// strict partition (every provider in exactly one), so the sum must be exactly 196.
// strict partition (every provider in exactly one), so the sum must be exactly 197.
const families: [string, string][] = [
["gateways", "APIKEY_PROVIDERS_GATEWAYS"],
["frontier-labs", "APIKEY_PROVIDERS_FRONTIER"],
@@ -71,7 +71,7 @@ test("APIKEY_PROVIDERS merges the 6 family files into 196 entries (no loss / no
seen.add(k);
}
}
assert.equal(famTotal, 196, "families must partition all 196 providers");
assert.equal(famTotal, 197, "families must partition all 197 providers");
});
test("AI_PROVIDERS Proxy aggregates all sections; lookups resolve", () => {

View File

@@ -40,6 +40,9 @@ test("runtime env helpers normalize runtime ports and conflicting color flags",
PORT: "22128",
DASHBOARD_PORT: "22128",
API_PORT: "21128",
// #8999 (docker entrypoint moved to /app) made withRuntimePortEnv also pin
// the bind address, defaulting to 0.0.0.0 when OMNIROUTE_HOSTNAME is unset.
HOSTNAME: "0.0.0.0",
}
);

View File

@@ -12,12 +12,12 @@ test("costs section exists in SIDEBAR_SECTIONS", () => {
assert.ok(section, "costs section must exist");
});
test("costs section has exactly 5 items in the correct order", () => {
test("costs section has exactly 6 items in the correct order", () => {
const section = findSection("costs");
assert.ok(section, "costs section must exist");
const items = sidebarVisibility.getSectionItems(section);
assert.equal(items.length, 5, "costs section must have 5 items");
assert.equal(items.length, 6, "costs section must have 6 items");
const itemIds = items.map((i) => i.id);
assert.deepEqual(itemIds, [
@@ -26,6 +26,7 @@ test("costs section has exactly 5 items in the correct order", () => {
"costs-budget",
"costs-free-tiers",
"free-provider-rankings",
"radar",
]);
});
@@ -42,6 +43,7 @@ test("costs section items have correct hrefs", () => {
{ id: "costs-budget", href: "/dashboard/costs/budget" },
{ id: "costs-free-tiers", href: "/dashboard/free-tiers" },
{ id: "free-provider-rankings", href: "/dashboard/free-provider-rankings" },
{ id: "radar", href: "/dashboard/radar" },
]);
});

View File

@@ -13,7 +13,10 @@ test("Dockerfile's --ignore-scripts npm ci is compensated for tls-client-node's
assert.match(
dockerfile,
/npm ci --no-audit --no-fund --legacy-peer-deps --ignore-scripts/,
// Flag-order tolerant on purpose: the assertion is about the --ignore-scripts
// PRECONDITION, not the exact flag list. #9185 inserted --include=optional
// (LLMLingua optional deps) and broke the literal pin without touching intent.
/npm ci(?: --[\w-]+(?:=[\w-]+)?)* --ignore-scripts/,
"expected the builder stage to install with --ignore-scripts (precondition of #7802)"
);