Aggregates every pending changelog.d fragment into the [3.8.49] section and
regenerates the contributors table from the reconciled bullets.
Three fixes this surfaced:
- The [3.8.49] section had no `### 📝 Maintenance` heading, so the aggregator's
findIndex matched the first one in the file — inside [3.8.47] — and would have
filed 92 maintenance bullets under the wrong release. Added the heading to the
living section; [3.8.47] stays at its original 234 bullets.
- 46 bullets carried no PR/issue reference. Fragments may keep the number only in
the filename (`<N>-slug.md`), which the aggregator does not copy into the bullet,
so the link and the credit were dropped on aggregation. Restored, scoped strictly
to the [3.8.49] range.
- 9 external contributors lost their attribution that way and are credited again:
@MisileLab (#8566), @MumuTW (#8619), @epsilonode (#8724), @hppsc1215 (#8835),
@sumanxg (#8837, #8856), @TitoTFP (#8838), @HouMinXi (#8842, #8845).
Contributors table: 84 → 155 entries, no one removed. 42 i18n mirrors synced.
check:changelog-integrity green — no base bullet lost.
- **feat(providers):** weekly quota for xAI OAuth (Grok) (`xai-oauth` / `xao`) via shared cli-chat-proxy billing (`creditUsagePercent`), with fail-open self-tracked fallback ([#8471](https://github.com/diegosouzapw/OmniRoute/pull/8471)) — thanks @allanvb
- **feat(sse):** every completion response now carries an `X-OmniRoute-Decision: strategy=<name>; provider=<alias>; latency_ms=<n>` header exposing the routing decision — `<name>` is the combo strategy (`priority`, `weighted`, `fusion`, etc.) or `single` for a non-combo request — for client-side debugging/analytics without server log access (#6022 — thanks @chirag127).
- **feat(ws):** the live-dashboard WebSocket server now auto-starts in-process (via `instrumentation-node.ts`) across every deployment mode — dev, production, Docker, Electron — with no separate sidecar script; the default WS port moved from 20129 to **20132** to avoid colliding with `API_PORT` in split-port setups, the deprecated `OMNIROUTE_DISABLE_LIVE_WS` env was consolidated into `OMNIROUTE_ENABLE_LIVE_WS` (default enabled), and the WS path is now derived from `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL`'s pathname (`/live-ws` fallback) (#6072 — thanks @ianriizky).
- **feat(dashboard):** Replace free-text model inputs in the Routing (web search route), Combo Defaults (handoff model), and Background Degradation tabs with a `hidePaidModels`-aware `ModelSelectField`, add a fail-open "paid-only pattern" warning to the per-model routing rule pattern field, and reject paid-only model targets at save time on `PATCH /api/settings`, `PATCH /api/settings/combo-defaults`, and `PUT /api/settings/background-degradation` when `hidePaidModels` is on ([#6540](https://github.com/diegosouzapw/OmniRoute/issues/6540))
- **feat(compression):** new **omniglyph** engine (context-as-image) — renders system prompt, tool docs, and dense history as compact PNG pages the model reads instead of text (~10× fewer tokens on the converted block; 59–70% end-to-end measured). Works stacked with RTK/Caveman (`stackPriority: 90`) or standalone (`mode: omniglyph`); restricted to Claude Fable 5 over the direct Anthropic route, fail-closed gates with `skip:<reason>` techniques, preview (`stable: false`, off by default) (#6556). Dependency bumped to `omniglyph@^1.0.2` for upstream ReDoS fixes (#6661).
- **feat(sse):** rate-limit request queue admission control — `resilienceSettings.requestQueue.maxQueueDepth` (default `0` = disabled, opt-in 0–100000) fast-rejects a request with a typed `RATE_LIMIT_QUEUE_FULL` error once the local per-provider+connection queue already holds `maxQueueDepth` requests, instead of growing the queue unboundedly; the factory default for `requestQueue.maxWaitMs` (how long a request may wait before being dropped) also fell from 120s to 15s so a saturated queue fails fast (#6593 — thanks @chirag127).
- **feat(sandbox):** the skill sandbox gained a container-provider abstraction that auto-detects and uses the best native runtime per host — Apple Container (macOS 26+), WSL container (`wslc.exe`), OrbStack, Podman — instead of hardcoding `docker run`, removing the Docker Desktop requirement on macOS/Windows (#6611 — thanks @KooshaPari).
- feat(oauth): accept the full ChatGPT session JSON (not just a bare access token) when pasting Codex credentials manually or via `POST /api/oauth/codex/import-token` (#6636)
- feat(sse): add 5 no-key g4f.space gateway providers — `g4f-groq`, `g4f-gemini`, `g4f-pollinations`, `g4f-ollama`, `g4f-nvidia` — a free, no-signup reverse proxy (gpt4free project) fronting Groq, Gemini, Pollinations, Ollama, and NVIDIA NIM, rate-limited to 5 req/min per IP (#6650 — thanks @chirag127).
- feat(sse): add DeepInfra as a video-generation provider via its native synchronous inference endpoint (#6653)
- feat(providers): add Rev AI speech-to-text provider with async job upload/poll/transcript flow (#6655)
- **feat(providers):** add **Segmind** as an image + video generation provider — `x-api-key` auth against `POST https://api.segmind.com/v1/{model}`, with a curated starter model list (Flux, Stable Diffusion XL/3.5, Kandinsky for image; Wan, Hunyuan, LTX, Kling for video) (#6656).
- feat(providers): add Gladia as an async speech-to-text provider (#6657)
- feat(video): add Novita AI as a video-generation provider (Wan/Kling async submit-poll) (#6658)
- **feat(providers):** add Speechmatics as an STT provider — async batch transcription (Enhanced operating point), 8 hours/month free tier, no credit card required. Streaming (real-time) mode is out of scope for v1. (#6659)
- feat(providers): add Mixedbread AI as an embeddings provider (`mxbai-embed-large-v1`, `mxbai-embed-2d-large-v1`, free tier) (#6660)
- **feat(providers):** add Felo (felo.ai) as a free, no-signup, no-API-key chat/search-agent aggregator provider (`felo-web`) — joins the existing `-web` family (DuckDuckGo AI Chat, Blackbox, etc). Five models (`felo-chat`, `felo-search`, `felo-scholar`, `felo-social`, `felo-document`) map to Felo's search categories; the executor opens a search thread then translates Felo's bespoke SSE stream into OpenAI-compatible chunks (#6666).
- **feat(providers):** add gTTS (Google Translate TTS) as a free, no-signup audio-speech provider — routes through Google's current `batchexecute` RPC endpoint (the previously proposed `translate_tts` endpoint is deprecated), splitting input at the 100-char-per-request limit. (#6667)
- **feat(sse):** add EdgeTTS (Microsoft Edge "Read Aloud") as a free, no-API-key `audio-tts` provider — the first WebSocket-transport speech provider, with per-client-IP rate limiting. (#6668)
- feat(providers): add FreeTheAi as an OpenAI-compatible gateway provider with a free Discord-signup tier (#6670)
- feat(sse): add Microsoft Designer as an unofficial web-session image provider, reverse-engineered submit-then-poll DallE.ashx flow (#6672)
- **feat(providers):** add Hailuo Web (`hailuo-web`) — a free, `_token`-based web-cookie chat provider for the MiniMax consumer chat product at hailuo.ai, ported from the g4f reference implementation (MD5-chain request signing, custom `event:`/`data:` SSE parsing). Distinct from the existing paid API-key `minimax`/`minimax-cn` providers. (#6673)
- **feat(cli):** new `omniroute auth export` command dumps DECRYPTED provider credentials (`apiKey`/`accessToken`/`refreshToken`/`idToken`) for one connection (`--id <id>`) or all connections, as `json` or `env` (`--format`), local-only and gated behind `--force` — no DB access happens without it, a stderr warning banner prints before any plaintext, `--out <file>` writes with `0600` permissions, and per-field decrypt failures surface as a `<field>DecryptFailed` boolean instead of aborting the export or leaking the caught error text (#6683)
- **feat(mitm):** the AgentBridge static MITM server (`server.cjs`) can now issue a per-host TLS leaf from a persisted local root CA (`src/mitm/cert/rootCa.ts`, reusing the CA/leaf crypto already proven for TPROXY in `tproxy/dynamicCert.ts`) instead of a single static self-signed leaf scoped only to the 4 antigravity hosts — a fresh install covers the full `MITM_TOOL_HOSTS` set automatically; an install that already trusted the old leaf keeps using it until the operator opts in via `MITM_ROOT_CA_ENABLED=true` (`src/mitm/cert/migration.ts`), so no existing install is silently upgraded to the more powerful any-host-signing CA trust model (#6684).
- **fix(sse):** skip `thinkingConfig` for Gemma models on the OpenAI→Gemini path so OpenAI-shape clients no longer get a 400 from Vertex. (thanks @chy1211)
- **feat(xai):** route xAI clients to Grok's native `/v1/responses` endpoint instead of the chat-completions bridge. (thanks @ryanngit)
- **feat(models):** add a Settings → AI "Model Overrides" UI plus `/api/model-capability-overrides` CRUD and a `model_capability_overrides` table, letting operators set a manual max-output-token override per provider/model (#6727 — thanks @xz-dev).
- **feat(api):** add `Vary: Accept-Encoding` to token-authenticated `/v1*`/`/v1beta*` responses so downstream caches distinguish compressed vs uncompressed variants (RFC 9110 §12.5.5). (thanks @chirag127)
- feat(sse): add Notion AI Web (Unofficial/Experimental) cookie-session provider (#6758)
- **feat(dashboard):** add per-routing-combo compression-mode override to the Compression Combos page under Context & Cache, alongside the existing combo-card quick override. (#6760)
- **feat(resilience):** operator-configurable account rotation policy — a new `rotationConfig` layer lets operators tune how connections rotate on failure, wired into `accountFallback` (#6763 — thanks @artickc).
- **feat(sse):** preserve `tools`/`tool_choice` for tool-bearing requests through fusion combos — bypass panel synthesis and route straight to the judge with tools intact (#6771 — thanks @chirag127).
- **feat(codex):** Codex provider model discovery now fetches the live catalog from `chatgpt.com/backend-api/codex/models` using Codex-shaped headers, falling back to a GitHub-hosted model manifest and then to the local static catalog when the live/GitHub sources are unavailable or return an unexpected shape — new `src/app/api/providers/[id]/models/discovery/codex.ts` (normalization, version-gating, merge/enrich against the local catalog) covered by `tests/unit/provider-models-discovery-split.test.ts` and `tests/unit/provider-models-route-codex.test.ts` (#6776 — thanks @JxnLexn).
- **feat(cursor):** register the Opus 4.8, Fable 5, and Sonnet 5 model families for the Cursor Agent provider so the latest Claude/Fable model ids route correctly (#6779 — thanks @andrewmunsell).
- **Changelog fragments (`changelog.d/`)**: PRs now add their changelog entry as a new fragment file (`changelog.d/{features|fixes|maintenance}/<PR>-<slug>.md`) instead of editing `CHANGELOG.md` — two PRs never touch the same file, structurally eliminating the CHANGELOG-eat merge conflicts that forced a re-sync push + full CI re-run after every sibling merge (O(N²) CI runs in a merge-storm). `scripts/release/aggregate-changelog.mjs` (npm run changelog:aggregate) folds fragments into the living section at release reconciliation, and `check:changelog-integrity` now also validates fragment well-formedness. Regression guard: `tests/unit/changelog-fragments.test.ts`.
- **feat(proxy):** add a latency-optimized proxy rotation strategy that ranks pool entries by measured round-trip latency, extending the existing round-robin/random/sticky proxy-pool selection (#6798 — thanks @iamraydoan).
- feat(db): include `xp_audit_log` in the automatic retention/prune cycle, with a configurable `retention.xpAuditLog` setting (#6801)
- **feat(fusion):** the fusion judge may now draw on its own knowledge and override the panel when every panel answer is wrong or incomplete, instead of being restricted to synthesizing only from panel output (#6804 — thanks @chirag127).
- **feat(dashboard):** search box on the Playground's raw model `<select>` (#4086) — the shared `ModelSelectModal` (combo builder + CLI-code cards) already had search, but Playground's `StudioConfigPane` model dropdown stayed a flat unsearchable list, unusable once a provider like OpenRouter contributed 50+ models. Typing now filters the dropdown (Turkish-safe accent/case-insensitive match via `matchesSearch`), while the currently selected model always stays pinned in the list even if it no longer matches the query, so typing never silently swaps the active selection. Reuses the existing `common.search` i18n key (already translated in all 42 locales) — no new translation key needed. Regression guard: `tests/unit/playground-model-selection-3731.test.ts` (`filterModelsByQuery`), `tests/unit/ui/playground-model-search-4086.test.tsx`. ([#6811](https://github.com/diegosouzapw/OmniRoute/pull/6811))
- **feat(usage):** Antigravity/agy quota widget now surfaces the **weekly** window alongside the existing per-model 5-hour window ([#4017](https://github.com/diegosouzapw/OmniRoute/issues/4017)) — the weekly limit isn't part of the per-model `retrieveUserQuota` response the fetcher already calls; it only appears in a separate, undocumented `retrieveUserQuotaSummary` RPC that groups models into families ("Gemini Models", "Claude and GPT models") with one weekly bucket per family. A new `usage/antigravityWeeklyQuota.ts` leaf fetches that RPC (cached, best-effort — a failure or unavailable RPC never breaks the existing per-model quotas) and parses the weekly bucket per group into `gemini_weekly`/`claude_gpt_weekly` quota entries, merged into the same `quotas` map the widget already renders generically. Regression guard: `tests/unit/antigravity-weekly-quota-4017.test.ts` (bucket parsing, the alternate `quotaSummary`-nested envelope, and end-to-end merge via `getUsageForProvider`). ([#6818](https://github.com/diegosouzapw/OmniRoute/pull/6818))
- **feat(codex):** Codex CLI compatibility shim — the Responses API `response.created`/`response.in_progress`/`response.completed` payloads now carry a `model` field (previously absent), and for Codex-CLI-originated requests it echoes the client-requested, effort-suffixed model id (e.g. `gpt-5.5-xhigh`) instead of the bare upstream id (`gpt-5.5`), so the Codex CLI status line/model button shows the active reasoning effort ([#3697](https://github.com/diegosouzapw/OmniRoute/issues/3697)). `openaiToOpenAIResponsesResponse` (`open-sse/translator/response/openai-responses.ts`) now threads the upstream model into the Responses event objects; a new `isCodexOriginatedHeaders()` (`open-sse/config/codexIdentity.ts`, reusing PR #3481's `originator`/User-Agent detection) makes chatCore's existing opt-in `echoRequestedModelName` (#1311) model-echo pipeline fire automatically for Codex clients regardless of the setting, detected by request headers so it still applies when `codex/gpt-5.5-xhigh` is routed through a combo to a non-codex upstream; `echoModelInObject`/`echoModelInSseLine` (`open-sse/services/responseModelEcho.ts`) now also rewrite the nested `response.model` field the Responses API uses. `/v1/models` still returns `models: []` for Codex (unchanged). Regression guard: `tests/unit/codex-effort-model-echo-3697.test.ts`. ([#6820](https://github.com/diegosouzapw/OmniRoute/pull/6820))
- **Z.ai Web (free web-session provider)**: new `zai-web` web-cookie provider drives the free chat.z.ai consumer chat UI via a pasted browser session cookie, distinct from the existing API-key `zai`/`glm`/`glm-cn`/`glmt` providers (`api.z.ai`) — modeled on the `doubao-web`/`venice-web` cookie executors and the pre-existing `chatglm-web` credential requirement/token-extraction entries. `ZaiWebExecutor` (`open-sse/executors/zai-web.ts`) posts to `chat.z.ai/api/chat/completions` with the cookie forwarded both as `Cookie` and as `Authorization: Bearer <token>`, and normalizes both z.ai's internal `delta_content`/`phase` SSE envelope and a pass-through OpenAI-shaped `choices[].delta` frame into standard chat-completion chunks. Registered in `WEB_COOKIE_PROVIDERS`, `WEB_SESSION_CREDENTIAL_REQUIREMENTS`, the provider registry (`zai-web` entry, GLM-4.6/4.5/4.5V models), and `tokenExtractionConfig.ts` for in-app cookie capture. Regression guard: `tests/unit/executor-zai-web.test.ts` (16 tests — token extraction, frame parsing for both SSE shapes, streaming and non-streaming aggregation, error paths). (#4056) ([#6823](https://github.com/diegosouzapw/OmniRoute/pull/6823))
- feat(dashboard): import multiple, possibly different providers from a CSV/JSON file — per-row validation, a checklist to pick which parsed rows to import, and a new `POST /api/providers/import` route with partial-failure results (#6836)
- **feat(compression):** update the vendored **GCF** codec behind the **Headroom** engine to spec **v3.2 (nested flattening)** ([#6837](https://github.com/diegosouzapw/OmniRoute/issues/6837)). Homogeneous arrays whose rows carry nested objects/arrays now tabularize via `>`-prefixed path fields instead of a low-yield per-row fallback, so nested MCP tool-result rows (`meta:{...}`, `tags:[...]`) compact like flat rows. On representative shapes the update takes deeply-nested payloads the old codec left near-uncompressed from ~3% to ~32% vs JSON (k8s pods, `cl100k_base`), with shallow-nested rows seeing a small bump and flat arrays unchanged. Re-vendored from current gcf-typescript (zero runtime deps, MIT, SPDX-marked, generic-profile only); also folds in the `[N]:` inline-array quoting fix and canonical decimal formatting. Round-trip stays lossless (order-insensitive), and the decoder is hardened against prototype pollution (a `__proto__`/`constructor` path segment never mutates `Object.prototype`, and keys shadowing built-ins like `toString` now round-trip correctly instead of misparsing). Regression guard: `tests/unit/compression/headroom-smartcrusher.test.ts` (deep-nested + prototype-pollution cases).
- **feat(sse):** OpenRouter quota tracking — a dedicated fetcher polls `/api/v1/key` + `/api/v1/credits` (per-key credit cap/remaining/reset, daily/weekly/monthly USD spend, BYOK usage) with a 45s cache and graceful degradation, a local per-account counter tracks the `:free`-model 50-or-1000-per-day + 20 RPM windows (corrected from `X-RateLimit-*` headers and `Retry-After` on 429), and OpenRouter `402` responses now lock the connection with a real cooldown instead of triggering an immediate reselection of the same credit-exhausted key (#6842).
- **feat(sse):** live gRPC-web quota fetcher for Grok Build (`grok-cli`) — polls xAI's shared weekly credit pool (`grok.com/grok_api_v2.GrokBuildBilling/GetGrokCreditsConfig`) with the connection's existing bearer token, hand-decoding the framed/raw protobuf response (proto3 omission = 0% used, malformed/expired-token fails open) instead of relying solely on the static 864 req/day estimate, which now serves as an explicit fallback when the live fetch is unavailable (#6844).
- feat(sse): add dual-window quota tracking for the `v0-vercel` provider — polls the credits (`/v1/user/billing`) and daily Platform-API operation (`/v1/rate-limits`) endpoints with the existing routing API key, defensively degrading to an `unknown` billing type rather than misparsing a future v0 billing-model migration, feeding preflight + the dashboard's Provider Quota card (#6845).
- feat(sse): add a static local RPM budget (default 40/min, operator-overridable), per-model 429 scoping (already covered by #6773's `passthroughModels`), and a per-connection concurrency cap (default 6, operator-overridable) for the `nvidia` (NVIDIA NIM) provider, which sends no rate-limit headers and has no usage API — Phase 1 of client-side quota tracking; adaptive AIMD learning and the dashboard quota card are deferred follow-ups (#6846).
- feat(sse): add quota tracking for the `agentrouter` provider — polls the New-API `/api/user/self` balance endpoint with a separate System Access Token + `New-Api-User` id (configured via `providerSpecificData.consoleApiKey` / `newApiUserId`), converting raw quota units into a dollar balance and feeding preflight + the dashboard's Provider Quota card (#6850).
- **feat(providers):** Add GPT-5.6 support across OpenAI API, Codex, and ChatGPT Web, including Codex Max/Ultra efforts, VS Code metadata, Fast-tier credit accounting, curated live discovery, the Codex 0.144.1 client identity, and correct chat routing for models that also support image generation ([#6862](https://github.com/diegosouzapw/OmniRoute/pull/6862)) - thanks @backryun
- **chore(providers):** Align emitted Claude Code identity headers, bridge fingerprints, provider profiles, and documented defaults with claude-cli 2.1.207 ([#6862](https://github.com/diegosouzapw/OmniRoute/pull/6862)) - thanks @backryun
- feat(api): add a structured `X-Routing-Fallback-Reason` header to relay routing responses, exposing a stable machine-readable reason code alongside the legacy `X-Routing-Fallback` detail string (#6872)
- **feat(api):** new **GET /api/usage/model-latency-stats** management endpoint exposes the existing rolling per-provider/model latency aggregate (avg/p50/p95/p99, success rate) already used internally by auto-combo routing — supports `windowHours`/`minSamples`/`maxRows`/`provider`/`model` filters (#6873).
- **feat(providers):** add a `vibeproxy-openai` provider-node preset to `POST /api/provider-nodes` — defaults name/prefix/apiType for VibeProxy's local OpenAI-compatible gateway and normalizes the caller-supplied base URL to its `/v1` root; `baseUrl` remains mandatory. (#6874, idea from #6137 by @KooshaPari)
- feat(usage): add avgTtftMs/avgE2ELatencyMs/avgTokensPerSecond to `getModelLatencyStats()` and feed them into auto-combo's speed-ranking factor (#6875)
- feat(sse): per-model default `reasoning_effort` (`ModelSpec.defaultReasoningEffort`, injected only when the request carries no reasoning field) and make `no-think/` express `reasoning_effort:"none"` instead of deleting the field on the OpenAI path, so thinks-by-default models actually stop thinking (#6879)
- **feat(providers):** let a custom/openai-compatible connection opt into prompt-cache behavior via a per-connection `cache` capability override, unblocking `prompt_cache_key` injection, the compression cache-aware guard, and `cache_control` passthrough for `openai-compatible-chat-<uuid>`-style connections. (thanks @andrea-kingautomation)
- **feat(dashboard):** add a Type filter (No Signup / OAuth Login / API Key) and an "Easiest first" sort toggle to Free Provider Rankings, so zero-setup NOAUTH providers can be surfaced without eyeballing the Type column. (#6915)
- **feat(providers):** expose an editable base-URL field on the ComfyUI connection so Docker-network setups (e.g. `http://comfyui:8188`) work for image, video, and music generation ([#6928](https://github.com/diegosouzapw/OmniRoute/issues/6928))
- **feat(providers):** refresh the curated OpenRouter embeddings catalog (`open-sse/config/embeddingRegistry.ts`) with the current lineup — `openai/text-embedding-3-small`/`-large`, `qwen/qwen3-embedding-8b`/`-4b`, `baai/bge-m3`, `mistralai/mistral-embed-2312`, `google/gemini-embedding-001` — and fold curated embedding/rerank entries into OpenRouter's live model-discovery response (`src/app/api/providers/[id]/models/route.ts`), additively and deduped by id, so they no longer only appear on the no-config `local_catalog` fallback. OpenRouter serves embeddings via a dedicated `/api/v1/embeddings` endpoint (omitted from `/v1/models`), so the live-discovery success path previously returned chat models only ([#6976](https://github.com/diegosouzapw/OmniRoute/issues/6976)). Regression guard: `tests/unit/openrouter-embeddings-catalog-6976.test.ts`.
- **feat(quota):** Add opt-in auto-ping to keep Codex quota windows warm — per-connection toggle in Settings → AI that sends a tiny request right after a Codex session window resets, so it isn't cold on the next real request ([#6977](https://github.com/diegosouzapw/OmniRoute/issues/6977))
- **feat(oauth):** Add a one-click browser (PKCE) login for Grok Build (`grok-cli`) ALONGSIDE the existing device-code flow — reusing the same `auth.x.ai` authorize/token endpoints and public client id as the sibling `xai-oauth` provider on its own loopback port — while keeping the pre-existing device-code method and the paste-token/`auth.json` import flow both available; the connect modal lets the user pick "Device Code", "Browser Login", or "JWT Token" ([#7013](https://github.com/diegosouzapw/OmniRoute/issues/7013))
- **feat(sse):** Add optional-enum `null`-omission idiom for Responses-API (codex) strict-mode tool schemas, closing the #6951 follow-up ([#7023](https://github.com/diegosouzapw/OmniRoute/issues/7023))
- **feat(auth):** accept the `x-goog-api-key` header for client-facing auth so `gemini-cli` and other `@google/genai`-based clients can use OmniRoute as a native `/v1beta` gateway (#7034 — thanks @QRcode1337).
- feat(sse): add a local dual-window (5h + 7d, per-account) quota tracker for the `freemodel-dev` provider, feeding preflight + the dashboard's Provider Quota card with user-overridable request caps (`FREEMODEL_5H_REQUEST_LIMIT` / `FREEMODEL_7D_REQUEST_LIMIT`) since FreeModel publishes no usage API — phase 1 of #7075 (tracker + registration); live hot-path request metering and the forward-compatible endpoint prober are tracked as follow-ups.
- **feat(api):** opt-in scheduled auto-sync for free-proxy sources (`src/lib/freeProxyProviders/scheduler.ts`) — periodically re-runs the free-proxy provider `sync()` calls (iplocate, proxifly, oneproxy, webshare) that previously only ran via a manual `POST /api/settings/free-proxies/sync`, so a seeded pool no longer goes stale as free-proxy lists rotate; gated by `FREE_PROXY_AUTO_SYNC_ENABLED` (default `false`) and `FREE_PROXY_AUTO_SYNC_INTERVAL_MS` (default 30min, floor-clamped to 5min), reuses the existing `isBuildProcess()`/`OMNIROUTE_DISABLE_BACKGROUND_SERVICES` guards, and shares one `runFreeProxySyncCycle()` code path with the manual route; `GET /api/settings/free-proxies/stats` now also reports `autoSync: { enabled, intervalMs }` (#7079 — thanks @chirag127).
- **feat(kiro):** register the GPT-5.6 Sol/Terra/Luna model family (272k context window). (thanks @SemonCat)
- **feat(dashboard):** show the Codex subscription plan label in provider connection rows and the quota view, falling back to the plan captured at OAuth import when the live usage endpoint doesn't report one. (thanks @CarmeloCampos)
- **feat(dashboard):** add a "Reorder" button to provider connections that sorts them by availability (using OmniRoute's connection-cooldown/testStatus model), persisting the new priority order. (thanks @fzrilsh)
- **feat(dashboard):** add 180D and 365D periods to the Cost Explorer range selector. The new ranges thread through `parseCostRange`/`COST_RANGE_VALUES` and the `getRangeStartIso` handlers in the analytics and requests-by-provider-date usage routes, so cost/usage analytics can be viewed over a half-year and full-year window (#7213)
- **feat(sse):** GitHub Copilot Claude models now route through Copilot's native `/v1/messages` endpoint (prompt-cache token counts, no more lossy tool-call round-trip). (thanks @yidecode)
- **feat(mitm):** Antigravity MITM model mappings now support an optional per-model reasoning-effort override (Default/None/Low/Medium/High/XHigh) alongside the destination-model remap. (thanks @trfi)
- **feat(sse):** add native xAI Grok Imagine video generation provider — `xai/grok-imagine-video` on `/v1/videos/generations` using your own xAI key, instead of only via the kie proxy market. (thanks @anndev-69)
- **feat(cli):** add Grok Build CLI tool setup — writes a `[model.omniroute]` custom model into `~/.grok/config.toml` and restores your previous default on Reset. (thanks @rixzkiye)
- **feat(provider):** add Chenzk API OpenAI-compatible gateway. (thanks @CahyokPutraDev99)
- **feat(dashboard):** add a per-operator "hide this quota row" toggle to the usage dashboard's provider limit cards, persisted per-provider in settings. (thanks @nguyenha935)
- **feat(sse):** session affinity (`X-Session-Id` / `x-codex-session-id` / `x-omniroute-session`) now works for **any** provider, not just Codex — the `codex`-only early-return in `resolveSessionAffinityTtlMs()` was removed, and the global TTL setting was renamed `codexSessionAffinityTtlMs` → `sessionAffinityTtlMs` (dashboard label updated to "Session affinity") with a backward-compatible migration that carries over an existing Codex TTL as the new default (#7274 — thanks @tenshiak).
- **feat(sse):**`gemini-web` now honors the OpenAI `tools` array by wiring the existing `webTools.ts` prompt-emulation shim (already used by 11 other web-cookie executors) — the tool contract is serialized into the prompt typed into the Gemini web UI, and `<tool>{...}</tool>` blocks in the response are parsed back into `tool_calls` (malformed JSON degrades to ordinary chat content, never an error), including for streaming requests; `docs/reference/PROVIDER_REFERENCE.md` gained a "Tool calling" column (`native`/`emulated`/`none`) for providers with confirmed ground truth. `claude-web` still silently drops `tools` pending a separate emulate-vs-native design decision (#7286).
- **feat(eval):** added a router-eval harness (`npm run eval:router`, `eval:router:compare`, `eval:router:patch-compare`, `eval:router:search`, `eval:router:trends`, `check:router-eval`) that replays routing decisions — from NDJSON corpora or the `usage_history`/`call_logs` SQLite tables — into an AIQ (success/latency/cost) score, compares baseline vs. candidate router configs with a retained-run regression gate, and ranks Pareto-optimal candidates; a sibling tool to the existing `eval:compression` harness (#7318 — thanks @KooshaPari).
- **feat(services):** introduce a `ServiceProviderPlugin` contract (`src/lib/services/providerPlugins/`) that packages what was previously spread across `bootstrap.ts`'s inline `SERVICES[]` config and `serviceBackends.ts`'s manifest template into one shape per embedded-service backend; migrates `9router` through it end to end as the first proof, with `cliproxy`/`mux`/`bifrost` left completely unchanged on their pre-existing code paths — `cliproxyapi` migration, executor-routing consolidation, and manifest-path wiring are follow-up work (#7333).
- feat(routing): wire `interceptFetch` into the chat pipeline — `resolveInterceptFetch(provider, model)` (a structural twin of `resolveInterceptSearch`) and the new `open-sse/services/webFetchInterception.ts` module rewrite a provider-native `web_fetch` tool declaration into a synthetic `omniroute_web_fetch` function tool, dispatched through the existing tool-call execution path to OmniRoute's own `/v1/web/fetch` instead of the upstream provider running it natively; strictly opt-in per provider/model (undefined/off leaves the request byte-identical to today), with a new dashboard toggle on the provider detail page covering both `interceptSearch` and `interceptFetch` (#7339, Phases 3-4 of #3384)
- feat(dashboard): confirm before removing a single connection, naming the account, mirroring the existing batch-delete confirm UX (#7361)
- **feat(providers):** Add a first-class xAI OAuth PKCE provider for `api.x.ai` models, including Grok 4.5 and refresh-token rotation ([#7399](https://github.com/diegosouzapw/OmniRoute/pull/7399)) — thanks @fenix007
- **feat(dashboard):** surface in-product guidance for the Settings → Prompt Compression engines — each `engineCatalog.ts` entry now carries a `guidance` block (quality/latency tradeoffs, lossy flag, cache impact) sourced from `docs/compression/*.md`, rendered as an expandable per-engine detail with a "safe default" indicator for lossless engines (Session Dedup, CCR, Lite, Headroom) plus a link to the full compression guide (#7530).
- **feat(providers):** Complete GHE Copilot OAuth provider implementation with device-code flow, validation, error sanitization, and DNS provisioning mapping ([#7546](https://github.com/diegosouzapw/OmniRoute/pull/7546))
- feat(providers): grok-web's add-connection dialog now shows a proactive hint explaining that grok.com's `cf_clearance` cookie is pinned to the IP, User-Agent, and TLS fingerprint of the browser that earned it — on a 403, paste the exact browser User-Agent into the existing Custom User-Agent field under Advanced Settings and reuse the same IP/proxy (#7567)
- **feat(providers):** register `lmstudio` in the embedding provider registry (`open-sse/config/embeddingRegistry.ts`) — LM Studio's local OpenAI-compatible `/v1/embeddings` endpoint, no auth required, passthrough model list (a user-configured provider_node still takes priority). Previously any `lmstudio/<model>` embedding request failed with `"Unknown embedding provider: lmstudio"` even though the model appeared fine in `/v1/models` ([#7601](https://github.com/diegosouzapw/OmniRoute/issues/7601) — thanks @ekinnee). Regression guard: `tests/unit/lmstudio-embedding-provider-7601.test.ts`.
- feat(sse): honor a no-auth provider connection's **Excluded Models** field (`providerSpecificData.excludedModels`) in the auto-combo/fusion candidate pool builder, so an excluded no-auth model (e.g. `minimax-m3-free`) is filtered out upfront instead of only failing over after being picked (#7622).
- **feat(dashboard):** proxy-assignment UX quality-of-life fixes — the per-connection proxy badge now shows the saved proxy's **name** instead of always falling back to its hostname (so distinct proxies sharing a domain no longer render identically), the "Proxy Config" modal's saved-proxy dropdown is now alphabetized by name, and the modal defaults to the **Saved** tab (instead of Custom) whenever the target scope has no assignment yet and at least one saved proxy already exists (#7643 — thanks @tenshiak).
- feat(providers): `zai-web` (chat.z.ai) now wires live `/models` discovery through the shared PROVIDER_MODELS_CONFIG pipeline (cache → hardcoded local-catalog fallback), instead of only serving a static 3-model catalog whose ids had started 404ing upstream (#7678 — thanks @andrea-kingautomation). The `chat.z.ai/api/models` response shape and Bearer-only auth are unverified pending a live cookie check before merge.
- **feat(perplexity):** Refresh Perplexity Web model mappings and Search API filters while keeping `/v1/models` scoped to API-key validation instead of curated catalog discovery ([#7687](https://github.com/diegosouzapw/OmniRoute/pull/7687)) — thanks @backryun
- Generic OpenAI-compatible model sync now captures a provider's upstream `reasoning.supported_efforts` (in addition to the pre-existing flat `supportedThinkingEfforts` shape), surfaces it in the model catalog as `capabilities.effort_tiers`, and advertises `<prefix>/<model>-{effort}` request-time alias ids for catalog-only clients — mirroring the existing per-model reasoning-effort plumbing (#6879). Codex and Kimi are excluded since they already own their own effort-suffix mechanism.
- **feat(dashboard):** Kimi (Moonshot AI) official-partnership highlight on the providers dashboard — Kimi-family providers (`kimi-coding`, `kimi-web`, `moonshot`) now render first within their category/group, and their card gets a Kimi-blue (`#1783FF`) accent border/glow plus an "Official Supporter" badge; presentation-only, routing/fallback order is unaffected (#7775).
- feat(providers): let any provider connection — built-in or custom — be pinned to an explicit routing tier (free/cheap/premium) via a new `/api/settings/tier-config` route and an Advanced Settings tier selector; `TierCoverageWidget` now honors the override too (#7818)
- feat(routing): add a read-only `GET /v1/auto-combo/{channel}/candidates` endpoint listing an `auto/*` channel's candidate pool with live reachability (provider circuit breaker, connection cooldown, model lockout), plus a per-API-key candidate exclusion list enforced in the `auto/*` candidate-pool builder — users with no overrides configured see byte-identical routing to today (#7819).
- feat(providers): copilot-m365-web tone-selected model variants (#7872)
- **feat(shared):** Add canonical numeric coercion helpers (`toNumber`, `toNumberOrNull`, `toNumberArray`) in `src/shared/utils/numeric.ts` with table-driven tests and a lint rule barring new local `toNumber` definitions, then migrate the report/analytics tier (usage analytics route, provider metrics route, usage stats, usage history helpers, call-log formatting, cost-analysis A2A skill) off their local duplicates ([#7879](https://github.com/diegosouzapw/OmniRoute/issues/7879))
- feat(mcp): add a narrow `mcp:connect` API-key scope for the `/api/mcp/` LOCAL_ONLY carve-out (separate from `manage`/`admin`) and populate `authInfo.scopes` from the caller's real per-key scopes over HTTP/SSE so `scopeEnforcement.ts` prefers per-key scopes over the `OMNIROUTE_MCP_SCOPES` env fallback when enforcement is enabled (#7895).
- feat(dashboard): provider tab account search (id/tag/name/email substring, cross-page) + mirrored top pagination bar + pagination for the tagged/grouped connections view (#7937)
- Teach the model the CCR retrieve protocol (marker → `omniroute_ccr_retrieve`, verbatim 24-char hash, `dedup:ref` look-back) on the first marker, gated on MCP-tool capability (#8033)
- Add a per-model/endpoint compression exclusion filter: name model ids or `provider/model` patterns (`*` wildcard) that must never be compressed, checked before any engine runs so the request passes through byte-identical; configurable from a new Dashboard → Compression → Exclusions tab (#8034)
- **feat(i18n):** zh-CN terminology glossary + consistency gate + 提供商→提供者 normalization — one-shot pass across `src/i18n/messages/zh-CN.json` (679 substitutions) and `bin/cli/locales/zh-CN.json` (54) mirroring #8024's zh-TW pass, a new versioned glossary (`scripts/i18n/glossary/zh-CN.json`) and protected-terms list (`scripts/i18n/glossary/protected-terms.json`), a pure-function consistency check (`scripts/i18n/check-glossary-consistency.mjs`, `npm run i18n:check-glossary`) wired into CI as `i18n-glossary-zhcn`, and zh-CN added to the visual-QA harness default locales (#8038).
- **feat(dashboard):** The Codex quota card no longer shows latent, never-used per-feature rate-limit ceilings (e.g. `GPT-5.3-Codex-Spark`) as permanent 100% rows with a meaningless always-full reset — they now appear only once the feature is actually used. Per-feature window labels track the payload's `limit_name`, and each window is labeled by its real `limit_window_seconds` (so a 7-day `primary_window` shows "Weekly" instead of "Session"). ([#8051](https://github.com/diegosouzapw/OmniRoute/issues/8051))
- feat(providers): native Fish Audio TTS provider on /v1/audio/speech (#8099)
- **feat(codex):** add bounded multi-reference PNG/JPEG/WebP image editing (up to 8 references and 20 MiB decoded in aggregate) through the OpenAI-compatible `/v1/images/edits` route by reusing Codex Responses hosted-image tooling, OAuth, proxies, sanitized logs/errors, and OpenAI Images response formatting ([#8122](https://github.com/diegosouzapw/OmniRoute/pull/8122)) — thanks @xiaoyaner0201
- feat(db): persist caller session tag into call_logs for per-session cost attribution (#8249)
- feat(api): quota-aware fallback routing for web-fetch providers (#8297)
- feat(providers): map upstream reasoning-level metadata in openai-compatible discovery (#8347)
- **feat(providers):** weekly quota for xAI OAuth (Grok) (`xai-oauth` / `xao`) via shared cli-chat-proxy billing (`creditUsagePercent`), with fail-open self-tracked fallback ([#8471](https://github.com/diegosouzapw/OmniRoute/pull/8471)) — thanks @allanvb
- feat(jobs): `backup auto enable` schedule is now executed server-side — the cron expression, cloud/encrypt/retention settings written by the CLI are consumed by a new `startBackupScheduleJob()` tick registered alongside `startBudgetResetJob()` in `instrumentation-node.ts`; includes a zero-dependency 5-field cron matcher ([#8513](https://github.com/diegosouzapw/OmniRoute/issues/8513))
- **feat(ci):** automate ratchet shrink-banking so file-size/complexity caps stop outliving their files after successful shrinks (#8584) ([#8612](https://github.com/diegosouzapw/OmniRoute/pull/8612)) — thanks @MumuTW
- **feat(providers):** live monthly credit quota for Firecrawl (`GET /v2/team/credit-usage`) in Provider Limits / preflight ([#8759](https://github.com/diegosouzapw/OmniRoute/pull/8759)) — thanks @allanvb
- **feat(search):** add Firecrawl to `POST /v1/search` supported providers with two sources (web|news) ([#8814](https://github.com/diegosouzapw/OmniRoute/pull/8814)) — thanks @allanvb
- **feat(adobe-firefly):** reference-image attach for generate + OpenAI `/v1/images/edits` support (follow-up to #8006). Uploads sources to Firefly storage (`POST /v2/storage/image`), then submits `referenceBlobs` on 3P generate-async (nano multi-ref `usage:general`; gpt-image `usage:subject`). Wire matches live `firefly.adobe.com` captures. Also routes built-in edits to the same path (up to 4 refs).
- **Homologation suite**: new `npm run homolog` runs the full release-homologation battery against the deployed VPS — health/version parity, API + real SSE streaming with an ephemeral API key (created and revoked by the run), minimal-cost real-provider smoke (promptfoo generated from the live catalog), and a Playwright sweep that loads every dashboard route and exercises the API-key UI flow — emitting a unified CTRF report that backs the release STOP #2 checklist
- **feat(mcp):** add a read-only Local Corpus context source with bounded incremental text indexing, path-containment controls, an authenticated settings API, and three scoped MCP tools.
- feat(providers): notion-web live model discovery via getAvailableModels (spaceId + token_v2 cookie)
- Provider connections can now be shown or hidden individually on the Provider Quota page. The
visibility setting is available on each account in the provider detail view and does not affect
routing or account activation.
- docs(i18n): rewrite Russian README as a full native manual (structure aligned with modern EN/zh-CN, fixed relative asset links).
### ⚡ Performance
- **perf(db):** project columns + composite index in getProviderConnections ([#6918](https://github.com/diegosouzapw/OmniRoute/pull/6918)) — thanks @oyi77
@@ -506,6 +631,379 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
- **fix(gemini):** drop HARM_CATEGORY_CIVIC_INTEGRITY from the default Gemini safety settings (#8231) ([#8238](https://github.com/diegosouzapw/OmniRoute/pull/8238))
- **fix(backend):** word-boundary-safe tool-result truncation in lite compression mode (#8169) ([#8239](https://github.com/diegosouzapw/OmniRoute/pull/8239))
- **fix(providers):** filter unsupported family-fallback candidates against the provider catalog (#8134) ([#8240](https://github.com/diegosouzapw/OmniRoute/pull/8240))
- **fix(translator):** synthesize tool call chunks from `response.completed` batched output when upstream omits individual `output_item.added`/`done` events, fixing `finish_reason: "stop"` instead of `"tool_calls"` for agentic clients ([#180](https://github.com/diegosouzapw/OmniRoute/issues/180), [#3980](https://github.com/diegosouzapw/OmniRoute/issues/3980))
- **fix(api):** Vercel Relay deploy now checks the Deployment Protection (SSO) PATCH response and surfaces `ssoProtectionWarning` when Vercel rejects it, instead of silently activating a relay that later returns an undiagnosed `403 Access denied`. (thanks @ricatix) ([#1037](https://github.com/diegosouzapw/OmniRoute/issues/1037))
- **fix(oauth):** resolve Kiro AWS SSO cache client credentials by matching the token's own `clientId` (including tokens with a direct `clientId` field instead of `clientIdHash`) instead of a region/latest-expiry guess, fixing spurious "Bad credentials" on refresh when multiple stale SSO client registrations are cached (thanks @XCrag).
- **fix(combo):** streaming Claude responses whose content block opens (`content_block_start`) and closes with no usable text/tool_use — a shape some upstreams return for tool-heavy requests on HTTP 200 — are now detected by `validateResponseQuality`'s SSE peek and trigger combo failover instead of being forwarded to the client as a silent empty completion (thanks @heishen6). ([#1382](https://github.com/diegosouzapw/OmniRoute/issues/1382))
- **fix(codex):** strip regex `pattern` lookaround (lookahead/lookbehind) from tool JSON Schemas on the Codex/OpenAI native passthrough path — previously only the translated-request path coerced tool schemas, so a `pattern` like `^(?=.*@).+$` reached OpenAI unmodified and was rejected with `regex lookaround is not supported`. (thanks @evinjohnn) (#7100)
- **fix(cli):**`stopMitm()` now removes /etc/hosts DNS-spoof entries before killing the MITM server process, closing the window where a client's DNS still resolved a target host to `127.0.0.1` while nothing was listening there — the cause of `connect ECONNREFUSED 127.0.0.1:443` right after stopping the MITM proxy (thanks @dionisius95).
- **fix(sse):** Cursor Composer/Auto tool calls that separate the arg name and value with a space instead of a newline (e.g. `path /Users/.../test`) no longer produce empty-valued, malformed argument keys, fixing silent no-op Write/tool calls. (thanks @way-art)
- **fix(dashboard):** the "Custom Models" add/edit form now has a "Vision capable" toggle so a custom OpenAI-compatible model can be manually flagged as vision-capable when the provider's discovery metadata doesn't report an image input modality (thanks @nguyenphi37)
- **fix(combos):** fusion combos now reject an oversized panel (>40 models by default, tunable via `fusionTuning.maxPanel`) with a clean 400 before fanning out, instead of buffering dozens of concurrent full responses in memory and OOM-crashing the whole container. (thanks @fontvu)
- **fix(providers):** the OpenAI-compatible "Check" validation flow now surfaces a warning when the chat-completions probe returns `404` (e.g. `model_not_found`) instead of silently passing as `Valid` — a bogus/non-standard model id (Featherless/OpenRouter-style `vendor/model` typos) previously went undetected at Check time and only surfaced once a real request tripped the per-model lockout. (thanks @advane204f)
- **fix(dashboard):** include never-tested custom provider connections in the combo builder's active-provider list so their models load without requiring a manual connection test first. (thanks @fajarbossit)
- **fix(executors):** forward agent-supplied `X-Session-ID`/`X-Title` metadata headers to upstream providers — previously dropped for every client outside the `x-opencode-*` allowlist. (thanks @chitholian) (#7104)
- **fix(sse):** Antigravity streaming requests that hit a non-ok upstream response (e.g. a 403) no longer pipe the raw upstream bytes straight through to the client — a binary/non-UTF8 error body (observed as gzip-magic-byte garbage) is now routed through the same sanitized `buildAntigravityUpstreamError()` path the non-streaming branch already used, instead of corrupting the client-visible error message. Regression guard: `tests/unit/antigravity-streaming-error-body-sanitized-2461.test.ts` — thanks @Duongkhanhtool
- **fix(providers):** MiniMax Text-to-Image now works — a `minimax` image-generation provider (`minimax-image` format, `image-01`/`image-01-live` models) was registered, since MiniMax previously had entries in the music/audio/video registries but none in the image registry, so any MiniMax image-model request fell through to a 404/unmatched-format response. (thanks @felipeleite)
- **fix(cli):** the runtime self-heal now verifies a cached `better-sqlite3` native binary actually loads for the running Node before trusting it — the old check only inspected the file's magic bytes (ELF/Mach-O/PE header), so a binary built for a different Node ABI passed validation and segfaulted the process on first use instead of triggering a rebuild. (thanks @mrprohack) (#7105) ([#2493](https://github.com/diegosouzapw/OmniRoute/issues/2493))
- **fix(openai):** strip `reasoning_effort`/`reasoning` for GPT-5.x models on the raw `openai` Chat Completions surface when the request carries function `tools` — upstream rejects that combination with HTTP 400 ("Function tools with reasoning_effort are not supported ... Please use /v1/responses instead"), and the dashboard has no `reasoning_effort:"none"` override to work around it client-side — thanks @techsolutionmta
- fix(providers): preserve relayAuth for vercel/deno/cloudflare relay proxies referenced by-id from the no-auth-provider Proxy Pool dropdown (#5716)
- fix(providers): wire the Devin cloud-agent provider into the generic provider-page validator and static model catalog, matching the existing `jules` cloud-agent pattern (#6142)
- **fix(providers):** refresh Baidu ERNIE + Qianfan provider website URLs to current developer landing pages ([#6271](https://github.com/diegosouzapw/OmniRoute/issues/6271)) — thanks @TrackCrewGalore
- fix(providers): honor a provider-level proxy assigned to no-auth providers like MiMoCode Free (#6272)
- fix(api): merge tool_call continuation deltas that carry only `id` (no `index`) so tool-call arguments are no longer split/lost in request/response logs (#6276)
- **fix(providers):** modernize the `lmarena` provider for the Arena.ai rebrand — route chat through `arena.ai` create-evaluation with Chrome TLS impersonation, seed a static Direct-chat Text/Search + Image catalog, and keep the `lmarena`/`lma` wire id for back-compat ([#6280](https://github.com/diegosouzapw/OmniRoute/pull/6280)) — thanks @backryun
- **fix(providers):** web-provider model discovery updated — qwen-web uses the slash-terminated models endpoint (avoiding a blocked 307 redirect), and kimi-web matches the current request shape (POST with bearer + `kimi-auth` cookie replay) with its catalog refreshed to the current non-agent models (#6308 — thanks @janeza2).
- **fix(logs):** the request-log detail modal no longer reopens by itself after being closed — a stale in-flight detail refresh resolved after close and re-triggered the modal open state (#6323 — thanks @xz-dev).
- **fix(providers):** update SenseNova Token Plan support — register the token-plan model ids/constants and adjust the SenseNova registry so token-plan accounts route correctly (#6330 — thanks @xz-dev).
- fix(providers): give v0-vercel-web its own alias so its credentials are detected (#6343)
- fix(providers): route AgentRouter key validation through the CC wire image so a valid key no longer 403s as "Invalid API key" (#6377)
- fix(db): stop legacy log-archive migration from deleting the live app-logger directory and crashing startup on a stat/stream race (#6401, #6799)
- fix(docs): document Turbopack build memory tradeoff and `OMNIROUTE_USE_TURBOPACK=0` webpack fallback for RAM-constrained machines (#6409)
- fix(compression): surface silently-dropped stacked-pipeline steps (session-dedup, ccr) and stop the aggregate inflation guard from misfiring on a genuine no-op (#6479, #6480, #6491)
- fix(providers): honor the `max_token` capability override in the reasoning-token-buffer output cap (#6524)
- **fix(dashboard):** the onboarding tier-flow diagram rendered broken — its SVGs lived in the repo-root `images/` (not a served path); moved to `public/images/` so Next.js serves them (#6538 — thanks @ianriizky).
- **fix(routing):** the `auto` combo's no-auth candidate pool now honors a disabled provider connection's own `isActive=false` (the toggle on the main Providers grid card), not just the separate global `blockedProviders` setting — disabling opencode/mimocode/etc. via the grid toggle no longer leaves it in rotation ([#6557](https://github.com/diegosouzapw/OmniRoute/issues/6557)).
- **fix(sse):** server-tool literal names (e.g. `web_search`) are preserved in message history and `tool_choice` instead of being namespaced/rewritten, so follow-up turns referencing those tools keep working (#6586 — thanks @MikeTuev).
- fix(sse): sanitize non-Latin1 characters before embedding combo diagnostics in HTTP headers, preventing a ByteString crash on quality-check failure (#6612)
- fix(api): recognize OpenRouter reasoning/reasoning_details in non-streaming OpenAI-to-Claude conversion (#6623)
- fix(db): share one in-flight sql.js load across concurrent `preInitSqlJs()` callers to stop the boot-time thundering-herd re-decode of the whole database file (#6628)
- fix(db): unwrap lone named-parameter objects before `sql.js``stmt.bind()` so `@`/`:`/`$`-style named placeholders bind correctly instead of throwing "Wrong API use" (#6802)
- **fix(db): break probe-failed/restore loop on large storage.sqlite** (#6632 — thanks @KooshaPari).
- fix(ci): exclude check-test-masking.test.ts's own tautology fixtures from the diff-based test-masking gate and recognize `✗` in validate-release-green's failure-line detector (#6634)
- fix(routing): recognize Kimi-style "exceeded model token limit" 400 as context overflow so combo fallback continues to the next target (#6637)
- **fix(cli):** Claude Code installed via WinGet is now detected on Windows (the WinGet install path was missing from the binary lookup) (#6647 — thanks @enjoyer-hub).
- **fix(providers):** removed obsolete/defunct providers from the catalog (glhf, kluster, cablyai, inclusionai) (#6675 — thanks @backryun).
- **fix(sse):** requests rejected before `handleChatCore` (circuit-breaker/cooldown gate or combo with all targets exhausted) are now recorded in `usage_history` too, so a key whose traffic was entirely gate-rejected no longer shows "zero requests" in the per-API-key usage counter (#6698).
- fix(providers): reject chat-completions requests for cloud-agent-only providers like jules instead of silently mis-routing them to OpenAI's endpoint (#6699)
- **fix(sse):** unwrap bare `{function:{…}}` tools so OpenAI-shape clients no longer have tools silently dropped in Claude translation. (thanks @samir-abis)
- **fix(oauth):** stop merging distinct Codex OAuth logins that share an email but lack a verifiable account id, preventing silent token overwrite. (thanks @lucasjustinudin)
- **fix(codex):** detect "model at capacity"/overloaded errors embedded in a 200-OK SSE stream and surface them as a real error so account fallback rotates, instead of passing them through as a successful response. (thanks @ryanngit)
- **fix(volcengine):** clamp `max_tokens` to the VolcEngine Ark endpoint cap for the Kimi model so oversized values no longer 400. (thanks @whale9820)
- **fix(antigravity):** surface aborted/malformed Gemini tool calls (e.g. `MALFORMED_FUNCTION_CALL`) as an explicit non-`end_turn` finish reason instead of a silent clean completion. (thanks @anhdiepmmk)
- **fix(routing):** the reasoning-token headroom buffer clamps to the model's explicit output cap instead of inflating past it, and `getExplicitModelOutputCap` falls through to the registry/spec cap when a synced capability row exists without a numeric `limit_output` ([#6714](https://github.com/diegosouzapw/OmniRoute/pull/6714)) — thanks @xz-dev
- **fix(api):**`omniroute health` (and `health components`/`health watch`) returned `Error: HTTP 404` ([#6677](https://github.com/diegosouzapw/OmniRoute/issues/6677)) — `bin/cli/commands/health.mjs` called `apiFetch("/api/health", ...)`, a route that was moved to `GET /api/monitoring/health` (`src/app/api/monitoring/health/route.ts`) without updating the CLI; `src/app/api/health/` on disk only has `degradation/route.ts` and `ping/route.ts`, no top-level handler. `runHealthCommand()`/`runHealthComponentsCommand()` now call `/api/monitoring/health` and read its actual payload shape (`activeConnections`, `circuitBreakers: {open, halfOpen, closed}`, `memoryUsage`) instead of the old, nonexistent `requests`/`breakers`/`cache`/`memory` fields. Regression guard: `tests/unit/cli-health-monitoring-route.test.ts`. ([#6717](https://github.com/diegosouzapw/OmniRoute/pull/6717))
- **fix(startup):** webpack build broke on case-insensitive filesystems (macOS APFS default, Windows) with a casing-collision warning plus "not exported" errors in `StudioConfigPane.tsx`/`ChatTab.tsx` (#6584) — `src/app/(dashboard)/dashboard/playground/components/ReasoningControls.tsx` (the component) and `reasoningControls.ts` (the utils module) shared the same lower-cased stem in the same directory, and two importers used the extensionless form `from "./reasoningControls"`, the exact resolution path that becomes ambiguous once casing is folded. Renamed the utils module to `reasoningControlUtils.ts` (no collision) and updated the 3 import sites. Regression guard: `tests/unit/case-collision-6584.test.ts` (scans `src/`/`open-sse/` for any same-directory, case-only filename collision). (#6584) ([#6718](https://github.com/diegosouzapw/OmniRoute/pull/6718))
- **fix(build):** Turbopack production build emitted an "Overly broad patterns can lead to build performance issues" warning per entry point importing `src/lib/agentSkills/generator.ts` (603 warnings reported on v3.8.46, up from 379 on v3.8.45) ([#6582](https://github.com/diegosouzapw/OmniRoute/issues/6582)) — `generator.ts`'s `outputBase` is built as `path.isAbsolute(outputDir) ? outputDir : path.join(process.cwd(), outputDir)`, where `outputDir` is a runtime function parameter, not a compile-time literal, so Turbopack's build-time file-tracing analyzer can't statically narrow the several dynamic `readdirSync`/`rmSync`/`readFileSync`/`writeFileSync` call sites a few lines below and falls back to a project-wide glob; #6366's commit message claimed to "anchor the base path with a literal" but the shipped code never did. Since this fs access is legitimate and bounded (`skills/<id>/SKILL.md`, ~48 known IDs), `next.config.mjs`'s `turbopack.ignoreIssue` (Next.js 16.2+) now suppresses this specific, known-benign diagnostic, mirroring the existing `webpack.ignoreWarnings`/`isNextIntlExtractorDynamicImportWarning` precedent already in the same file for the webpack path. Regression guard: `tests/unit/next-config.test.ts` (asserts the `turbopack.ignoreIssue` rule shape targeting `src/lib/agentSkills/**`). ([#6720](https://github.com/diegosouzapw/OmniRoute/pull/6720))
- **fix(providers):** Codex Desktop requests to `gpt-5.3-codex-spark` failed with `[400]: Tool 'image_generation' is not supported with gpt-5.3-codex-spark`, even on paid-plan accounts ([#6651](https://github.com/diegosouzapw/OmniRoute/issues/6651)) — `CodexExecutor.transformRequest` (`open-sse/executors/codex.ts`) only dropped the Codex Desktop-injected `image_generation` hosted tool when `isCodexFreePlan()` matched the account's plan, with no awareness that Spark-scope Codex models reject `image_generation` upstream regardless of plan. `dropImageGeneration` now also drops it when `getCodexModelScope(model) === "spark"` (the existing Spark classifier from `open-sse/config/codexQuotaScopes.ts`), independent of account plan. Regression guard: `tests/unit/codex-spark-image-generation.test.ts` (thanks @alltomatos for independently catching and fixing it via #6819). ([#6721](https://github.com/diegosouzapw/OmniRoute/pull/6721))
- **fix(providers):** the provider quota card's weekly/session bars re-sorted by remaining percentage instead of staying in a fixed, deterministic order ([#6687](https://github.com/diegosouzapw/OmniRoute/issues/6687)) — `QuotaCardExpanded.tsx`'s `sortQuotasByRemaining()` (added in #5977) was applied unconditionally via `useMemo(() => sortQuotasByRemaining(quotas), [quotas])`, undoing the deterministic `CODEX_QUOTA_ORDER`/`GLM_QUOTA_ORDER` window order `quotaParsing.ts`'s `sortCodexOrder()`/`sortGlmOrder()` (added in #6336) already established for Codex and the GLM family — since #6336 never touched `QuotaCardExpanded.tsx`, the two orderings never composed, so e.g. a Codex `session` window with less headroom than `weekly` rendered after it instead of staying first. A new `hasFixedQuotaOrder()` (`quotaParsing.ts`) and `resolveQuotaDisplayOrder()` (`QuotaCardExpanded.tsx`) now skip the remaining-% re-sort for providers with a fixed window order, threading `providerId` from `QuotaCard.tsx` through to the display layer; every other provider still gets the remaining-% sort. Regression guard: `tests/unit/quota-card-expanded-fixed-order-6687.test.ts`. ([#6722](https://github.com/diegosouzapw/OmniRoute/pull/6722))
- **fix(i18n):** pt-BR was missing 194 UI keys present in `en.json` — a real, silent data-sync gap, not covered by any duplicate/mislabeled #6694 (that issue's 9 `providers.*` keys are disjoint, present-but-untranslated sentinels caused by a separate `providerText()` fallback bug) ([#6695](https://github.com/diegosouzapw/OmniRoute/issues/6695)) — `scripts/i18n/sync-ui-keys.mjs` (which mirrors newly-added `en.json` keys into every locale) wasn't re-run after recent `en.json` additions, and the CI `i18n:check-ui-coverage` gate only fails a locale below an 80% threshold, so pt-BR stayed green at 93.8% coverage despite the gap. Backfilled all 194 missing keys into `src/i18n/messages/pt-BR.json` (translated to Brazilian Portuguese, no leftover `__MISSING__` markers) via `npm run i18n:sync-ui -- --locale=pt-BR` + manual translation. Regression guard: `tests/unit/i18n-pt-br.test.ts` (new case asserting full `en.json`→`pt-BR.json` key parity, so a future drift fails a fast unit test instead of silently degrading the coverage percentage). ([#6723](https://github.com/diegosouzapw/OmniRoute/pull/6723))
- **fix(startup):**`omniroute --mcp` crashed at Node ESM link time with `ERR_MODULE_NOT_FOUND` for `ioredis` on installs where the published MCP bundle didn't happen to have `ioredis` rescued from a parent `node_modules` ([#6559](https://github.com/diegosouzapw/OmniRoute/issues/6559)) — `src/shared/utils/rateLimiter.ts` had a top-level static `import Redis from "ioredis"`; that module is only ever reached via a lazy `await import(...)` several call-sites deep in the MCP tool chain, but esbuild's `--packages=external` bundling of the MCP server (`scripts/build/prepublish.ts` Step 8.5) still hoisted rateLimiter.ts's own static import into a real top-level ESM import in the compiled `dist/open-sse/mcp-server/server.js`, forcing Node to resolve `ioredis` at module-link time — before any `--mcp` startup code runs — and `ioredis` is not guaranteed to ship in the MCP-only bundle's `node_modules`. `getRedisClient()` now lazily imports `ioredis` on first use (matching the established soft-dependency pattern in `src/lib/quota/redisQuotaStore.ts`) while still throwing synchronously when Redis isn't configured. Regression guard: `tests/unit/build/mcp-bundle-no-eager-ioredis.test.ts` (bundles the real MCP server entrypoint with the exact publish-time esbuild flags and asserts no top-level static `ioredis` import remains, while the pre-existing lazy `await import("ioredis")` in `redisQuotaStore.ts` stays intact). ([#6725](https://github.com/diegosouzapw/OmniRoute/pull/6725))
- **fix(providers):** Kiro sent the adaptive-thinking `additionalModelRequestFields` envelope for `claude-sonnet-4.5`/`claude-haiku-4.5`, which Kiro/CodeWhisperer rejects upstream with a raw `[400]: additionalModelRequestFields is not supported for this model` ([#6576](https://github.com/diegosouzapw/OmniRoute/issues/6576)) — `buildKiroPayload()` (`open-sse/translator/request/openai-to-kiro.ts`) gated the field on the generic Anthropic-API `supportsReasoning()` capability flag, which is `true` for both models on Anthropic's direct API but does not reflect what Kiro's CodeWhisperer backend actually accepts; only `claude-sonnet-5` is confirmed adaptive-thinking-capable there. A new Kiro-specific allowlist (`supportsKiroAdaptiveThinking()` in `open-sse/translator/request/openai-to-kiro/adaptiveThinking.ts`) now gates the envelope instead. Regression guard: `tests/unit/repro-6576-kiro-thinking-unsupported-model.test.ts`. ([#6726](https://github.com/diegosouzapw/OmniRoute/pull/6726))
- **fix(translator):** Cursor's local **Subagent** tool call is no longer rejected with `cloud_base_branch may only be specified when environment equals cloud` — the Responses→Chat tool-arg cleanup (`stripEmptyOptionalToolArgs`) was scoped to Claude Code's `Read` tool only, so Cursor's `Subagent` tool passed through with the cloud-only `cloud_base_branch: ""` (Cursor treats an empty string as "specified" and rejects the call before starting the local subagent). The cleanup now covers an allowlist of `Read` + `Subagent`; arbitrary tools are still left untouched (empty strings/arrays can be valid payloads for them). Regression guard: `tests/unit/openai-responses-subagent-strip-2446.test.ts`. (thanks @like3213934360-lab)
- **fix(translator):** GLM 5.2 (and other OpenAI-compatible upstreams that stream a tool call's `id` and `function.name` in **separate** SSE chunks) no longer produce an empty tool name / `No such tool available:` error through the Claude `/messages` path — the `openai-to-claude` streaming translator emitted `content_block_start` immediately on the id-only chunk with an empty `name`, and the Claude SSE protocol cannot patch a block after it is emitted, so the later name-only chunk was silently dropped. It now **defers**`content_block_start` until the tool name arrives (falling back to starting the block when arguments arrive first), so the emitted `tool_use` always carries the real name. Regression guard: `tests/unit/openai-to-claude-glm-split-tool-name-2077.test.ts`. (thanks @itiwant)
- **fix(resilience):** OmniRoute didn't respect an exhausted Ollama Cloud (or any other apikey-category provider) quota — it retried the account seconds later instead of waiting out the real reset window ([#6638](https://github.com/diegosouzapw/OmniRoute/issues/6638)) — `shouldPreserveQuotaSignalsFor429()`/`checkFallbackError()` (`open-sse/services/accountFallback.ts`) only applied body-text quota classification (daily/monthly/weekly quota-exhausted detection) to OAuth-category providers; apikey-category 429s (Ollama Cloud, OpenAI, etc.) always fell through to the generic short rate-limit cooldown regardless of what the error body said, and `parseRetryFromErrorText()` also had no support for day-granularity reset hints ("Your quota will reset in 3 days.") — only Xh/Ym/Zs combos. An explicit quota-exhausted signal in the body (`looksLikeQuotaExhausted()`) now overrides the apikey-category default via the new `shouldPreserveQuotaSignals()` (`open-sse/services/quotaResetParsing.ts`), and `parseDayGranularityResetMs()` parses whole-day reset countdowns so the real multi-day window is honored instead of a few seconds of backoff. Regression guard: `tests/unit/issue-6638-ollama-quota.test.ts` + 2 aligned `tests/unit/account-fallback-service.test.ts` cases that previously asserted the buggy rate_limit_exceeded/undefined-dailyQuotaExhausted behavior for apikey-provider quota text. ([#6731](https://github.com/diegosouzapw/OmniRoute/pull/6731))
- **fix(resilience):** a combo step "pinned" to one fingerprint account (mimocode/mcode/opencode multi-account providers) never actually resolved to that account, so it couldn't fail over when the pinned account was depleted ([#6696](https://github.com/diegosouzapw/OmniRoute/issues/6696), relates #6612) — the combo builder UI encodes an account pin as a composite connectionId (`${rowId}|fp|${fingerprint}`, `src/lib/combos/builderOptions.ts`), but `expandTargetsByFingerprints()` (`open-sse/services/combo/fingerprintExpansion.ts`) looked that composite string up directly in `connectionById` (keyed by real DB row ids), got `undefined`, and passed the target through unchanged, still carrying the bogus composite id — so downstream credential resolution could never match it either. `expandTargetsByFingerprints()` now splits the `|fp|` composite id back into the real connection row id + the pinned fingerprint (new `splitFingerprintPin()` helper) before any lookup, resolving the target to the real connectionId (with the pinned fingerprint carried on the new `pinnedFingerprint` field) instead of the inert composite string. Regression guard: `tests/unit/combo-fingerprint-pin-6696.test.ts`. ([#6732](https://github.com/diegosouzapw/OmniRoute/pull/6732))
- **fix(api):** Responses passthrough emitted event-only SSE frames (no `data:` line) for every dropped commentary event, breaking the OpenAI Python SDK's `sse.json()` parser ([#6561](https://github.com/diegosouzapw/OmniRoute/issues/6561)), follow-up to #6199/#6232 — the commentary-drop `continue;` branches in `open-sse/utils/stream.ts` skipped the `data:` line for a dropped commentary event but never cleared the already-buffered `event:` line for that same frame, so the next blank line flushed the stale `event:` line alone. Both drop sites now call `clearPendingPassthroughEvent()` before `continue`, discarding the buffered prefix along with the dropped payload; the commentary-drop decision itself was extracted into a new `open-sse/utils/responsesCommentaryDrop.ts` so the fix does not grow the frozen `stream.ts`. Regression guard: `tests/unit/responses-commentary-event-frame-6561.test.ts` (realistic `event:\ndata:\n\n` frames — the existing #6199 test only used bare `data:` lines and never exercised this path). ([#6735](https://github.com/diegosouzapw/OmniRoute/pull/6735))
- **fix(compression):**`/api/compression/preview`'s top-level `originalTokens`/`compressedTokens` diverged from `engineBreakdown[0]`'s counts for the same single-engine run (tiktoken outer counts vs the `JSON.stringify(...).length/4` estimate per engine), worst on small inputs. A new `reconcileSingleEngineTokens()` overwrites the single-engine breakdown entry with the outer, more accurate figures; multi-step pipeline breakdowns are left untouched ([#6488](https://github.com/diegosouzapw/OmniRoute/issues/6488)). Regression guard: `tests/unit/compression/preview-outer-engine-token-reconcile-6488.test.ts`. ([#6741](https://github.com/diegosouzapw/OmniRoute/pull/6741))
- **fix(resilience):** account selection could pick an account already out of quota upstream on every credentialed route except `chat`/`codex` ([#6686](https://github.com/diegosouzapw/OmniRoute/issues/6686)) — `getProviderCredentials()` (`src/sse/services/auth.ts`) only skips a connection when a *local cache* already flags it exhausted (`isQuotaExhaustedForRequest`/`src/domain/quotaCache.ts`); it never itself calls the registered upstream `QuotaFetcher`. Only `getProviderCredentialsWithQuotaPreflight()` performs that live upstream check, and it was wired into exactly 2 call sites (`src/sse/handlers/chat.ts`, `src/app/api/internal/codex-responses-ws/route.ts`) — every other credentialed route (`rerank`, `images/generations`, `images/edits`, `audio/transcriptions|speech|translations`, `videos/generations`, `music/generations`, `ocr`, `providers/[provider]/embeddings`, `providers/[provider]/images/generations`, `web/fetch`, `moderations`, `search`) called the plain, cache-only selector, so an account whose cache entry was never populated (e.g. its first request landed on one of these routes) could be selected even at 0% quota remaining. Those 14 call sites now go through `getProviderCredentialsWithQuotaPreflight()` instead, matching chat/codex coverage. Regression guard: `tests/unit/issue-6686-quota-preflight-coverage.test.ts` (static check that none of the routes call the plain selector anymore + a behavioral check that the preflight-aware selector blocks a 100%-used account). ([#6742](https://github.com/diegosouzapw/OmniRoute/pull/6742))
- **fix(api):**`reasoning_content` (extended-thinking text) was silently dropped from `/v1/chat/completions` SSE on the `claude-web` and `v0-vercel-web` executors ([#6662](https://github.com/diegosouzapw/OmniRoute/issues/6662)) — every chunk builder in both adapters hardcoded `delta: { content: ... }` with no reasoning path, unlike the established pattern already used by `default.ts`/`deepseek-web.ts`/`bedrock.ts` and the real-Anthropic-API `claude-to-openai.ts` translator (`thinking_delta` → `reasoning_content`). `v0-vercel-web.ts` now forwards an upstream `delta.reasoning_content` field (streaming and non-streaming) the same way `deepseek-web.ts` does. `claude-web.ts`'s `buildClaudeStreamingResponse` now maps a `content_block_start`(`type: "thinking"`)/`content_block_delta`(`delta.thinking`) pair onto `delta.reasoning_content`, and `claude-web/payload.ts`'s `transformToClaude()` no longer hardcodes `thinking_mode: "off"` — a new `wantsExtendedThinking()` derives it from the request's `reasoning_effort`/`reasoning.effort`/`thinking.type` signal, so extended thinking can actually be requested. Regression guard: `tests/unit/issue-6662-repro.test.ts` (RED→GREEN for both adapters). ([#6743](https://github.com/diegosouzapw/OmniRoute/pull/6743))
- **fix(api):** the compression config PUT schema now accepts `enableRenderers` for the RTK engine instead of rejecting the documented option (#6703, #6757 — thanks @alltomatos, with an independent duplicate fix from @chirag127 via #6756).
- **fix(api):** raised the provider `apiKey` length cap for cookie-based web providers, whose session-cookie credentials legitimately exceed the previous limit (#6715, #6759 — thanks @alltomatos).
- **fix(routing):** fusion combos no longer silently drop `combo-ref` panel members — a referenced combo is now dispatched as one black-box panel voice instead of being dropped (#6764)
- fix(ci): publish electron-updater `latest*.yml` manifests in electron release assets so auto-update can find them ([#6766](https://github.com/diegosouzapw/OmniRoute/issues/6766))
- **fix(i18n): translate hardcoded Portuguese dashboard strings to English (#6761, #6768)** (#6769 — thanks @chirag127).
- fix(providers): strip redundant node prefix when resolving custom OpenAI/Anthropic-compatible connections by raw connection id, preventing double-namespaced model ids from 400ing upstream (#6772)
- fix(providers): scope nvidia NIM 404s to the single failing model instead of cooling down the whole connection (#6773)
- **fix(codex):** bump the default Codex CLI client identity from `0.142.0` to `0.144.0` for compatibility with newer Codex-backed models ([#6780](https://github.com/diegosouzapw/OmniRoute/pull/6780)) — thanks @quanturbo
- **fix(ci):** the blocking "Impacted unit tests (TIA)" step false-redded any PR whose impact graph reached a dashboard component — it ran every selected test under `--import tsx/esm`, but `tests/unit/dashboard/**` requires the `--import tsx` CJS transform (ESM-only deep imports like `@lobehub/icons/es/*`), exactly as the canonical `test:unit:ci:shard` already does per segment. The impacted selection is now split by segment with matching loaders (closes #6787). ([#6788](https://github.com/diegosouzapw/OmniRoute/pull/6788))
- **fix(translator):** read PDF/video `file_data` attachments on the OpenAI→Gemini/Antigravity and OpenAI→Claude paths so multimodal documents (not just images) reach the upstream — PDFs map to `document`/`inlineData` and videos keep their `video/mp4` mime instead of being dropped (#6790 — thanks @Witroch4, with an independent report/fix from @samimozcan via #6762/#6753).
- **fix(providers): ensure DeepSeek Web SSE emits [DONE] after FINISHED** (#6791 — thanks @Pitchfork-and-Torch).
- **fix(api):** the compression config `PUT` schema (`stackedPipelineStepSchema`) now accepts every `ENGINE_CATALOG` id — the structural engines `session-dedup`/`ccr`/`headroom`/`relevance`/`llmlingua`/`omniglyph` and the `aggressive``ultra` intensity — so a `GET`→`PUT` round-trip of a stacked pipeline no longer 400s on a valid engine the discriminated union had omitted (#6747 — thanks @Pitchfork-and-Torch).
- **fix(cursor):** send the Agent CLI build id as `x-cursor-client-version` so Cursor upstream accepts requests from the current CLI build instead of a stale hardcoded version (#6795 — thanks @andrewmunsell).
- fix(cli): waitForServer() no longer reports ready from a raw TCP accept alone — requires a fast HTTP rejection or a real health response, so the "OmniRoute is running!" banner no longer fires 30-60s before the server can actually answer requests (#6800)
- **fix(codex): strip include from compact responses requests** (#6805 — thanks @yinaoxiong).
- fix(dashboard): surface Claude extraUsage credits in quota card when quotas is empty (#6806)
- **Request count by provider & date**: Dashboard → Analytics now shows a dedicated table of request counts grouped by provider and calendar date (plus token totals), for providers that bill per-request rather than per-token — sortable columns and a single-date filter. New `getProviderDailyUsageRows()` query (`src/lib/db/usageAnalytics.ts`) and its own `GET /api/usage/requests-by-provider-date` route (kept separate from the frozen `/api/usage/analytics` route). Regression guard: `tests/unit/db-provider-daily-usage-4009.test.ts`. (#4009 — thanks @tjengbudi) ([#6812](https://github.com/diegosouzapw/OmniRoute/pull/6812))
- **fix(resilience):** an Ollama Cloud (or any apikey-category provider) account that hit a weekly usage cap kept getting retried every few minutes instead of backing off ([#3709](https://github.com/diegosouzapw/OmniRoute/issues/3709)) — the upstream 429 body ("you (\<account\>) have reached your weekly usage limit") was invisible to `checkFallbackError`'s existing subscription-quota-text classifier (Issue #2321) because that branch is gated by `shouldUseQuotaSignal`, which is oauth-only, so apikey providers like `ollama-cloud` fell through to the generic exponential backoff (~1s, capped at 2min) — one account took 285x429 in 48h. A new `isWeeklyUsageLimitText`/`buildWeeklyQuotaFallback` classifier (extracted, with the existing subscription-quota logic, into a new `open-sse/services/quotaTextCooldowns.ts` module so the frozen `accountFallback.ts` didn't have to grow) runs unconditionally and applies a 24h `QUOTA_EXHAUSTED` cooldown regardless of provider category. Regression guard: `tests/unit/ollama-cloud-weekly-quota-cooldown-3709.test.ts`. ([#6817](https://github.com/diegosouzapw/OmniRoute/pull/6817))
- **fix(providers):** an explicit `thinking.budget_tokens: 0` is now honored in the OpenAI→Gemini transform (thinking disabled) instead of being treated as unset (#6813, #6821 — thanks @alltomatos).
- **fix(bootstrap):** filter empty `process.env` values before spawning embedded services so a blank env var no longer crashes the Docker bootstrap in a restart loop (#6828 — thanks @AndrianBalanescu).
- **fix(providers):** classify upstream `404` responses as `MODEL_NOT_FOUND` (model lockout) instead of a retryable provider error, stopping the retry storm when a single model is missing (#6829 — thanks @AndrianBalanescu).
- **fix(db):** cap the sql.js OOM-during-probe path in `getDbInstance()` at 3 attempts with a terminal diagnostic — previously only the generic-corruption probe-failure path had a cycle-breaker (#6632), so a persistently OOMing `storage.sqlite` probe re-threw the identical error forever on every call from every background poller, hanging the app with "Internal Server Error" and no self-recovery (#6835).
- fix(mcp): de-duplicate `TOTAL_MCP_TOOL_COUNT` by tool name instead of double-counting collections (#6854)
- **fix(usage):** xAI's exact provider-reported `cost_in_usd_ticks` no longer silently accepts `null`/`""`/negative values as a valid `$0` exact cost — `extractUsageFromResponse()` (`open-sse/handlers/usageExtractor.ts`) and `normalizeUsage()` (`open-sse/utils/usageTracking.ts`) now require `typeof value === "number" && Number.isFinite(value) && value >= 0` instead of coercing with `Number(x)`, so a malformed exact cost correctly falls back to the token-based estimate instead of masking it with a bogus `$0`. Regression guard: `tests/unit/xai-exact-cost-2453.test.ts` (rejects null/empty/negative exact costs on both call sites). ([#6856](https://github.com/diegosouzapw/OmniRoute/pull/6856) — thanks @KooshaPari)
- **fix(plugin):** the `@omniroute/opencode-plugin` dynamic provider hook stopped embedding its OC-1.17.8+-gate-compatible `opencode-`-prefixed provider id into model routing fields (`ModelV2.id`/`providerID`, combo catalog keys) — OmniRoute's server has no `opencode-<x>` provider alias, so every dispatched model failed credential lookup with "No credentials for opencode-omniroute" (#6859).
- fix(sse): apply cliproxyapiModelMapping at CLIProxyAPI dispatch time (#6876)
- fix(sse): defer `response.completed` until a trailing usage-only chunk arrives on `/v1/responses` streams (#6906)
- **fix(cli):** ship `head-response-guard.cjs` into the standalone bundle — `server-ws.mjs` imported it without a matching `EXTRA_MODULE_ENTRIES` entry, so every `build:release` dist crashed at boot with `ERR_MODULE_NOT_FOUND`; a new regression test derives the required sidecars from `server-ws.mjs` imports (#6908)
- fix(sse): wire the shared quota-fetch throttle into DeepSeek, Bailian, OpenCode, and Crof quota fetchers, not just Codex (#6911)
- fix(sse): rename client-sent `max_completion_tokens` to `max_tokens` for providers/models that only accept the legacy field (e.g. Volcengine Ark / DeepSeek), mirroring the existing reverse rename from #1961 (#6912)
- fix(providers): `PROVIDER_LIMITS_SYNC_SPACING_MS` now also throttles local / API-key (Ollama) connections, not just OAuth — spaced between concurrency chunks so a local endpoint isn't hit by a simultaneous refresh burst (#6916)
- fix(sse): classify LAN embeddings providers (10/8, 192.168/16, CGNAT) as no-auth instead of forcing bearer auth (#6925)
- fix(sse): Qwen Web executor no longer sends `[object Object]` when a message uses structured (array) content — the text parts are now flattened (#6927)
- perf(api): relay chat-completions routes now thread the already-fetched `RelayToken` into `checkRateLimit`, skipping a redundant `SELECT * FROM relay_tokens WHERE id = ?` re-query on every request (#6930)
- fix(sse): `normalizeCodexMessageContentPart` now rewrites explicit `type: "input_text"` (not just `type: "text"`) to `output_text` on assistant-role Codex Responses input parts, so replayed assistant history sent by codex-cli as `input_text` is no longer rejected by the Codex/OpenAI backend (#6932)
- fix(sse): omit removed `attachments` field from Muse Spark Web (Meta AI) persisted GraphQL query to fix `Unknown type "AttachmentInput"` 502s (#6935)
- fix(dashboard): label audio/embeddings/image compatible providers by kind instead of "Chat" on ProviderCard (#6936)
- fix(api): model-list discovery for LAN-local OpenAI-compatible providers (e.g. LM Studio) now uses the same local-first SSRF guard as the connection test, instead of the stricter guard that always blocked LAN hosts (#6939)
- fix(providers): `openai->gemini` transform now maps `reasoning_effort: "none"` to `thinkingConfig.thinkingBudget: 0` (with `includeThoughts: false`), giving callers an explicit, documented off-switch for Gemini thinking; the no-knob-at-all default injection (#4170) is unchanged (#6813, thanks @rafaumeu)
- **fix(sse):** escape backslash before brackets in ChatGPT-web citation link text, preventing a citation label containing `\` from corrupting the generated Markdown link ([#6944](https://github.com/diegosouzapw/OmniRoute/pull/6944)) — thanks @brick30llc-ctrl
- fix(oauth): tokenHealthCheck now lowercase-normalizes `conn.provider` before checking `ROTATING_REFRESH_PROVIDERS.has()` and the GitHub Copilot sub-token refresh guard, so mixed-case provider values (e.g. "OpenAI", "Github") no longer bypass the rotating-refresh-token skip or the Copilot sub-token refresh (#6947)
- fix(sse): make Codex Responses tool-arg normalization schema-aware — drop values equal to the tool's declared JSON Schema `default`, generalize empty-optional stripping to any tool (not just `Read`/`Subagent`) via `schema.required`, and thread each tool's schema from the request's `tools[]` into the streaming response translator (#6951)
- fix(sse): drop internal commentary-phase Responses output in TRANSLATE-mode streams, not just PASSTHROUGH — codex/Responses-upstream routes translated into another client format (e.g. Claude Code) no longer leak duplicate prose and narrated tool-call arguments into the client text channel (#6952)
- fix(sse): stop forwarding empty-signature thinking blocks verbatim to Anthropic-native legs, which permanently poisoned combo fallback (#6953)
- **fix(sse):**`perplexity-web`'s default `pplx-auto`/`pplx-sonar` targets now post `mode: "copilot"` (with `pplx-sonar` mapped to `model_preference: "turbo"` instead of `experimental`), match Perplexity's `event: end_of_stream` SSE terminator instead of OpenAI's `[DONE]`, and fall back to the COMPLETED frame's double-encoded FINAL step-blob when no `diff_block`/`markdown_block` streamed — fixing `[perplexity-web/pplx-sonar] Provider returned empty content` against the current schematized `www.perplexity.ai` SSE API. (thanks @artickc)
- **fix(combos):** embeddings-only and rerank-only models (e.g. JinaAI, Gemini auto-imported, OpenRouter custom, reranker models) no longer disappear from the combo builder's model picker — the leftover chat-only `isChatCapable` gate in `addModelOption()` has been removed (#6975).
- **fix(combos):** when 2+ distinct model ids from the same provider would render an identical display name in the combo builder picker (e.g. Mistral's `codestral-latest`/`codestral-2508` aliases sharing one upstream catalog name), each colliding entry now falls back to its own id as the display label so every row stays visually distinguishable and findable (#6957).
- **fix(dashboard):** the combos builder now hides provider connections the user has explicitly disabled, instead of relying only on stale test-status (#6984 — thanks @attid).
- **fix(providers):** cap grok-cli tools at 200 per request, matching xAI's cli-chat-proxy limit, and document the non-reasoning capability of grok-build/grok-composer-2.5-fast in the registry (#6986, thanks @gitcommit90)
- fix(providers): DuckDuckGo AI Chat executor propagates the real upstream status (429 rate limit with `Retry-After`) instead of misclassifying VQD-token acquisition failures as a hardcoded 503 (#6996)
- fix(providers): refresh OpenCode (`oc`) free-tier model catalog — 6 delisted IDs replaced with the 4 currently-live free models (#6998)
- fix(api): raise the main server's `keepAliveTimeout`/`headersTimeout` well above Node's 5s default so pooled keep-alive clients (e.g. JetBrains AI Assistant's JVM `HttpClient`) stop getting 0 bytes back on a reused connection (#7003)
- fix(compression): wire the adaptive context-budget "dial" (`contextBudget`) into the settings schema and DB so it can actually be persisted via `PUT /api/settings/compression`, instead of being silently rejected (#7005)
- fix(usage): stop opencode-go quota lookup from defaulting to an unrelated Z.AI endpoint (#7022)
- **fix(providers):** Auggie (Augment CLI) model registry updated to the real v0.32.0 CLI model IDs, with a live `auggie model list` auto-discovery fallback for future renames; the old pre-v0.32.0 IDs (`claude-sonnet-4.6`, `claude-opus-4.6`, `claude-haiku-4.5`, `gemini-3.1-pro`, `gemini-3.0-flash`, the `gpt-5.4`/`gpt-5.5` high/medium variants) are a **breaking rename**, but a backward-compat alias map in `resolveAuggieModel()` transparently remaps them to their v0.32.0 equivalents so existing saved combos keep working without any manual update (#7032 — thanks @oyi77).
- fix(ci): add a dashboard-scoped typecheck gate covering `src/app/(dashboard)` TSX, previously invisible to `typecheck:core` and `next build` (#7033)
- **fix(cli):**`omniroute dashboard` (no `--port` flag) now respects `PORT` from the environment instead of always opening `localhost:20128`, matching `serve`/`launch` precedence (`--port` > `PORT` env > `20128` default) (#7049 — thanks @kaon0388v1).
- fix(build): extend the Turbopack `ignoreIssue` suppression to `open-sse/services/compression/**`, matching the `getModuleDir()` dynamic-path fs pattern already suppressed for `src/lib/agentSkills/**` in #6582, eliminating the remaining 610 "Overly broad patterns" warnings (#7051)
- fix(providers): web-cookie connection-test/cookie-validation probe (zai-web and every other registry-entry web-cookie provider) now honors the configured HTTP/SOCKS proxy — the `/models` probe routed through `directHttpsRequest`'s hardcoded native-fetch bypass, silently skipping proxy resolution even though the executor's actual chat traffic already respected it (#7058)
- fix(resilience): recognize Ollama Cloud's 5-hour session usage-limit 429 as quota-exhausted instead of a generic rate limit (#7071)
- fix(dashboard): restore mobile single-column fallback on the Provider Quota page card grid, fixing clipped labels/buttons on phone-width viewports (#7072)
- fix(dashboard): include proxyId when testing a saved registry proxy so SOCKS5/auth credentials are loaded (#7080)
- **fix(sse):** xiaomi-tokenplan `mimo` models (e.g. `mimo-v2.5-pro`) are now recognized as thinking-mode upstreams that require `reasoning_content` echoed back on every assistant turn, fixing a persistent `400 reasoning_content must be passed back` error on multi-turn conversations ([#7098](https://github.com/diegosouzapw/OmniRoute/pull/7098)) — thanks @xxue-z
- **fix(dashboard):** align onboarding tier descriptions and localize the tier step header and flow copy ([#7125](https://github.com/diegosouzapw/OmniRoute/pull/7125)) — thanks @Wibias
- **fix(sse):** claude-web now surfaces the real upstream error body for non-SSE 400/403/429/500 responses instead of reporting "no response body" — the streaming client was discarding the already-captured temp-file bytes and reading the native binding's empty in-memory body field instead (#7134).
- fix(db): honor combo-level proxy assignments from the registry when resolving a connection's proxy (#7149)
- fix(dashboard): wire modelAliases fetch into HermesAgentToolCard so OpenRouter and other passthrough providers appear in the Hermes Agent role picker (#7151)
- fix(dashboard): filter hidden custom models out of the legacy combo model picker (#7156)
- fix(dashboard): Agent Bridge DNS toggle now sends POST (was PUT), fixing HTTP 405 on Start/Stop DNS (#7157)
- fix(dashboard): implement missing `handleToggleSource` callback on the Free Pool tab so the page no longer crashes with a `ReferenceError` (#7161)
- fix(sse): stop duplicating text in Gemini Web streamed responses (#7163)
- **fix(executors):** Codex Responses Lite requests force serial tool calls required by the upstream API ([#7171](https://github.com/diegosouzapw/OmniRoute/pull/7171)) — thanks @fenix007
- **fix(translator):** preserve Gemini thinking-mode `thought:true` parts as `reasoning_content` instead of leaking them into visible assistant text on the OpenAI request bridge. (thanks @warelik)
- **fix(translator):** register the missing OpenAI→Gemini response projection so combo-routed OpenAI-native providers no longer leak raw `chat.completion.chunk` shapes to Gemini-format clients. (thanks @warelik)
- **fix(cli):**`omniroute --version` now fast-paths before the tsx/esm + polyfill imports, env-file loading, and Commander's full command registration, cutting local runtime from ~1.5s to ~0.3s. (thanks @Jordannst)
- fix(ci): build dast-smoke and nightly API-only smoke workflows with `OMNIROUTE_BUILD_BACKEND_ONLY=1` to skip the unused dashboard UI graph and stop the multi-minute build variance/timeouts (#7226)
- **api:** bulk-add API keys no longer overwrite existing provider connections — a colliding auto- or custom-generated name now gap-fills a free suffix instead of silently replacing a saved connection's key/state. (thanks @asynx6)
- fix(sse): feed the compression pipeline the authoritative vision capability instead of the conservative model-id heuristic, so vision models absent from the fragment list (e.g. gpt-5.5) no longer have their image_url blocks silently stripped (#7237)
- **fix(sse):** route the public OpenAI GPT-5.6 family (`gpt-5.6`, `-sol`, `-terra`, `-luna`) through the Responses API — Chat Completions rejects GPT-5.6 requests that combine function tools with an active `reasoning_effort`. (thanks @Jordannst)
- **fix(providers):** honor a configured proxy on Grok Build egress — the grok-cli executor used raw `https.request()` and bypassed the proxy context, leaking the host IP on chat inference and OAuth token refresh. (thanks @ryanngit)
- **fix(nvidia):** expand NIM chat model catalog with newly-observed models. (thanks @spacesky-cell)
- **fix(sse):** synthetic bypass responses for Claude-format clients no longer drop their content — `mergeChunksToResponse()` now reconstructs the message from streamed content blocks instead of returning an empty array. (thanks @KunN-21)
- **fix(build):** isolate Windows HOME/AppData during next build. (thanks @KunN-21)
- fix(dashboard): providers model-name filter now matches an aggregator's live/synced catalog, not just the static curated registry (#7250)
- fix(docs): correct stale `/api/version` and migration-125 references + realign `no-explicit-any` suppression counts drifted by base-red realignment commits (#7253)
- fix(sse): project non-streaming JSON responses back to the Gemini/Antigravity `{response:{candidates}}` envelope instead of leaking the raw OpenAI `choices[]` shape, so tool calls are no longer dropped for Gemini-family clients on the JSON path (#7255) (thanks @warelik)
- fix(i18n): treat `__MISSING__:` sync-script placeholders as absent so the EN fallback renders instead of the raw sentinel (#7258)
- fix(authz): classify /api/cli-tools/forge-settings and /api/cli-tools/jcode-settings as LOCAL_ONLY, closing an RCE-via-tunnel gap where getCliRuntimeStatus() spawns a child process without loopback enforcement (#7263)
- fix(sse): lazy-load playwright in claudeTurnstileSolver so unsupported platforms (e.g. Termux/Android) don't crash on boot (#7265)
- **fix(sse):** stop logging a caller-initiated request abort/timeout as a noisy proxy transport failure in `proxyFetch`. (thanks @TuyulSpam)
- fix(sse): classify 401 "model X is not supported" as model-not-found so it locks the model out instead of looping forever (#7268)
- fix(dashboard): resolve `ReferenceError: t is not defined` crashing `/dashboard/costs` when a filtered slice has zero-cost rows (#7272)
- fix(cli): Windows MITM root-CA check/uninstall keyed off the hardcoded legacy hostname `daily-cloudcode-pa.googleapis.com` instead of the actual generated CA's identity — they now derive a SHA-1 thumbprint from the real `certPath` file (same pattern `#6338` used for the DNS side of this anti-pattern) (#7275)
- fix(cli): reuse cliRuntime's win32-aware `locateCommand`/`shell:true` probe in tool-detector so installed CLIs (npm `.cmd` shims) are no longer reported as absent on native Windows (#7279)
- fix(dashboard): connection Test surfaces a rate-limit warning on 429 chat-probe responses instead of an unqualified pass (#7284)
- fix(sse): combo failover now detects OpenAI-shape streams truncated without `finish_reason`/`[DONE]` (#7285)
- **fix(db):**`getDbInstance()` now guarantees sql.js WASM has already been pre-initialized (via a top-level await in `src/lib/db/core.ts`) before ANY consumer can reach it, closing an ordering gap where early startup steps (`ensureSecrets()`, `clearStaleCrashCooldowns()`, `getSettings()`, `initAuditLog()`) called `getDbInstance()` before `ensureDbReadyForBoot()` had a chance to run `preInitSqlJs()` — turning a recoverable driver failure into a hard boot crash (`sql.js WASM ainda não foi pré-inicializado`) whenever both `better-sqlite3` and `node:sqlite` failed to open an existing `storage.sqlite`. `tryOpenSync()` also now logs the real underlying cause of each swallowed sync-driver failure instead of an empty `catch {}`. (#7288, #7494)
- fix(sse): split effort/reasoning suffix off pinned Claude/GPT model ids before sending to cursor's server (#7289)
- fix(sse): hoist client-injected `system` messages to index 0 for strict OpenAI-compatible providers (xiaomi-mimo) regardless of origin (#7293)
- fix(sse): treat Uint8Array/Buffer as opaque binary in log redaction to stop per-byte enumeration on Bedrock Converse image requests (#7297)
- fix(cli): load DATA_DIR/server.env as a fallback for .env when migrating from Electron, so STORAGE_ENCRYPTION_KEY/JWT_SECRET/API_KEY_SECRET survive an Electron→CLI install migration (#7302)
- **fix(rerank):** Honor the connection's pinned proxy on rerank calls, so a provider that geo-blocks the host IP (Voyage AI) works on the same connection where chat and embeddings already did ([#7350](https://github.com/diegosouzapw/OmniRoute/issues/7350)) — thanks @kamenkadmitry
- fix(chatgpt-web): recognize `update_content.messages[]` (plural array) celsius WebSocket frames so async image_gen pointers are no longer silently dropped (#7357)
- fix(sse): clamp glm-4.6v max_tokens to the 32768 ceiling for zai and glm providers, wiring stripUnsupportedParams into GlmExecutor's own transform path (#7364)
- fix(sse): honor per-model targetFormat override for zai/glm-coding-apikey buildUrl and make custom-model id lookup case-insensitive (#7364)
- fix(sse): combo session stickiness now releases a connection whose per-window quota is exhausted, matching the provider-level session-affinity pin (#7387)
- fix(cli): log Codex Responses WebSocket history/usage per logical turn instead of once per connection (#7388)
- fix(embeddings): remove the non-existent `voyage-multilingual-3.5` from the embedding registry and add the missing Voyage models (#7425 — thanks @kamenkadmitry)
- fix(providers): expose a base-URL override for Kimi/Moonshot so CN-region API keys (issued on platform.kimi.com / moonshot.cn) can be pointed at api.moonshot.cn instead of being rejected by the international host (#7447)
- fix(build): align `engines.node` supported range (>=22.22.2 <23 || >=24.0.0 <27) across package.json, lockfile, README engine references and the node-runtime support test, so install-time engine checks match the actually-tested runtimes (#7446)
- fix(db): stop closing the single sql.js singleton during getDbInstance()'s probe/reopen sequence, which poisoned every boot with "Database closed" once both sync SQLite drivers were unavailable (#7494)
- fix(sse): stop stream readiness from treating a choices-less mid-stream error frame as a successful stream, so combo can fail over instead of returning zero `choices` (#7503)
- Fixed the Codex connection **Test** button always reporting success for ChatGPT-account tokens: the probe used `gpt-5.3-codex`, a codex-only model ChatGPT accounts reject with a 400 — the same status the probe treats as "auth OK", so a bad token was indistinguishable from a good one. It now probes with `gpt-5.5`, a model ChatGPT-account sessions actually support (#7521).
- The Codex account import (`POST /api/oauth/codex/import`) now validates each record's `refresh_token` against OpenAI's OAuth endpoint before persisting the connection: an already-invalidated session (`refresh_token_invalidated` / a dead `auth.json`) is rejected with a clear "run `codex login` again and re-import" message instead of importing as `active` and failing confusingly on first use. Valid tokens import as before, with any rotated tokens applied (#7522).
- The PKCE OAuth start (`/api/oauth/[provider]/start-callback-server`, used by Codex/Windsurf/Devin) now detects when OmniRoute is being driven from a remote host and returns a reverse-tunnel hint (`remoteHost`, `tunnelCommand`, `message`) instead of hanging silently: the callback server binds the *server's* localhost:PORT, so a browser on a different machine would redirect to its own localhost and never complete. Loopback access is unchanged (#7523).
- fix(providers): search providers now expose a static model catalog derived from `searchTypes`, fixing "does not support models listing" 400 for serper-search, brave-search, perplexity-search, exa-search, tavily-search, google-pse-search, youcom-search, searxng-search, zai-search (#7529)
- fix(sse): map `tool_search` to a Chat function tool instead of dropping it during Responses->Chat translation (#7532)
- fix(sse): gate `verbosity`/`prompt_cache_key` on OpenAI destination during Responses->Chat translation, stopping the leak to non-OpenAI upstreams like NVIDIA (#7533)
- fix(api): Usage page "by provider" table now shows the configured provider display name (e.g. "OpenAI Codex") instead of the raw internal provider id (e.g. "codex") (#7534)
- fix(api): Usage page "model usage" table no longer lists the same logical model twice when it was recorded under both a bare and a provider-prefixed spelling (e.g. `glm-5.2` and `z-ai/glm-5.2`) — the in-memory dedup key now uses the normalized model name (#7535)
- fix(codex): non-stream Codex (ChatGPT-account) chat no longer 502s with "Response body is already used". `peekCodexSseTransientError` now checks the content-type before touching `response.body`: on the wreq-js TLS-fingerprint transport the Response is backed by a native body handle and merely accessing `.body` disturbs it, so the empty-content-type non-stream response was being consumed by the peek guard and then re-read by `readNonStreamingResponseBody`. Streaming was unaffected. Validated live on the VPS (`codex/gpt-5.5` + `codex/gpt-5.6-terra` non-stream now return 200) (#7536)
- **fix(sse):** preserve `input_image` parts in array-valued Codex `custom_tool_call_output` payloads instead of rewriting them to invalid `output_text` parts ([#7540](https://github.com/diegosouzapw/OmniRoute/pull/7540)) - thanks @loulanyue
- fix(providers): stop reporting Arena (lmarena) cookie validation as Invalid when the `/models` probe hits a 307 redirect — degrade to `unsupported` instead, and drop the stale "no registry entry" comment for lmarena (#7542)
- **fix(combo):** streaming combo failover now fails over when an upstream SSE response is truncated mid-lifecycle with no recognised terminator (`data: [DONE]`, `finish_reason`, `message_stop`/`message_delta` with `stop_reason`, or a terminal `usage`-only chunk) and no structured SSE frame at all — previously any byte, even unparseable garbage, satisfied the generic done-branch gate and the truncated stream was passed through, leaving the client hung waiting for events that never arrived ([#7545](https://github.com/diegosouzapw/OmniRoute/pull/7545)) — thanks @Chewji9875
- **fix(dashboard):** Public and managed tunnel endpoints now take precedence over loopback URLs in dashboard setup and copyable API configuration ([#7547](https://github.com/diegosouzapw/OmniRoute/pull/7547)) — thanks @nguyenha935
- fix(claude-web): unify Turnstile solver, executor and httpBackedChat fast-path User-Agents behind one shared fingerprint module so `cf_clearance` is never solved under a different UA than the one that replays it (#7548)
- fix(cli): fall back to the node:sqlite driver cascade in `bin/cli/sqlite.mjs` so `omniroute doctor` no longer reports a false "FAIL Database"/"FAIL Storage/encryption" on machines without a working better-sqlite3 native binary (#7586)
- fix(api): expose Responses-API-format (OpenAI/Codex) chat models on every VS Code Ollama-compatible listing route, not just `/models` (#7587)
- **grok-cli:** OAuth paste-import now requires the full `~/.grok/auth.json` (with `refresh_token`) instead of a bare JWT `key`, preventing connections that can never auto-refresh (#7610).
- fix(sse): proactively refresh Grok Build's expiring OAuth token before dispatch, and add it to the connection-test config so "Test Connection" no longer reports "unsupported" (#7610)
- **grok-cli:** sanitize Responses `function_call_output.output` values before dispatch so incomplete `\u` escapes / malformed tool results no longer cause intermittent 400 body-parse failures on cli-chat-proxy (#7611).
- **fix(translator):** guard against double-emission when `response.completed` echoes `function_call` items already streamed via incremental `output_item.added`/`done` events — skip synthesis for `call_id`s already tracked, preventing duplicate tool call chunks for incrementally-streaming providers
- fix(routing): strip `prompt_cache_key` for NVIDIA NIM — Codex CLI injects it, NIM's OpenAI-compatible wrapper 400s on it (#7617)
- fix(routing): honor eye-icon hidden models for no-auth providers in auto-combo candidate pools (#7620)
- fix(providers): correct Chutes registry baseUrl from api.chutesai.com to llm.chutes.ai (#7621)
- fix(providers): unify connection status across dashboard surfaces, deduplicate provider cards, preserve no-auth model discovery for metadata-only rows, and route playground models through the provider alias (#7629)
- fix(providers): classify Mistral ambiguous 401 (quota vs revoked key) instead of asserting hard auth failure (#7638)
- fix(sse): route CLIProxyAPI fallback/passthrough legs through a dedicated `cliproxyapi_api_key` credential instead of the failed native provider's own key (#7645)
- fix(packaging): move fumadocs-mdx from dependencies to devDependencies to avoid pulling its build-only yuku-analyzer/yuku-ast toolchain into `npm install -g omniroute` (#7661)
- GET /api/combos/auto now enumerates template variants (auto/best-coding, auto/pro-_, auto/claude-_, auto/best-free) and family variants (auto/glm, auto/llama, auto/gemini, etc.) in addition to the flat and suffix variants it already exposed. Previously these 27 variants were advertised by /v1/models but missing from the combos endpoint, causing 404s when clients tried to use them.
- fix(sse): persist rotated Gemini web-session cookies via onCredentialsRefreshed (#7676)
- fix(dashboard): mirror connection-row action-icon spacing under RTL via Tailwind logical utility (#7680)
- fix(cli): split `outboundUrlGuard.ts`'s DB/feature-flag helpers into `outboundUrlGuardPolicy.ts` so `omniroute setup-opencode` no longer crashes with `Cannot find package '@/shared'` on a global npm install (#7682)
- fix(sse): wire settings.wildcardAliases into model resolution so wildcard model aliases created in Settings actually take effect (#7693)
- fix(mcp): copy undici into dist/node_modules to prevent hollow-package shadowing crash (#7701)
- **fix(translator):** sanitize `tool_result.tool_use_id` symmetrically with the assistant's `tool_use.id` in the OpenAI→Claude request translator — a raw tool-call id containing characters outside `[a-zA-Z0-9_-]` (e.g. dots, colons) previously diverged from its sanitized `tool_use.id` counterpart, causing `enforceToolResultAdjacency()` to silently drop the `tool_result` and Anthropic to reject the request with HTTP 400 (#7705)
- **fix(db):** bulk proxy imports now update an existing proxy when only its password changes instead of creating a duplicate ([#7707](https://github.com/diegosouzapw/OmniRoute/pull/7707)) — thanks @floze-the-genius
- fix(oauth): stop Codex OAuth completion from collapsing distinct accounts that share an email but have no workspaceId, by requiring chatgptUserId agreement before merging (#7737)
- fix(db): purge in-memory API-key health/rotation state when a provider connection is deleted (#7740)
- **perf(db):** the 5 provider-connection/node call sites [#7787](https://github.com/diegosouzapw/OmniRoute/pull/7787)'s IC2 conversion missed — `providers/[id]/models`, `v1/provider-plugin-manifest`, `localHealthCheck.ts`, `sync/bundle.ts` — now read through `getCachedProviderConnectionById`/`getCachedProviderNodes`; `tokenHealthCheck.ts` keeps uncached `getProviderConnectionById` reads at the unrecoverable-refresh-error and GitHub Copilot sub-token-refresh call sites to avoid a CAS-staleness regression. ([#7744](https://github.com/diegosouzapw/OmniRoute/pull/7744) — thanks @oyi77)
- fix(compression): CCR no longer collapses a whole prompt into a bare, unretrievable `[CCR retrieve hash=...]` marker on non-MCP upstreams — a short preamble of the original text is always kept alongside the marker (#7746)
- fix(sse): strip orphaned tool_use blocks before dispatching to Antigravity/Vertex Claude models, mirroring the mainline Claude executor's #2382 guard (#7752)
- fix(cli): resolve Windows CLI detection false negatives — nvm-windows symlinked binaries no longer rejected as symlink_escape, and a stray known-path artifact no longer hides a genuinely runnable Claude CLI on the PATH fallback (#7753, #7774)
- fix(dashboard): keep session/weekly quota rows in a stable fixed order on collapsed provider-quota cards, matching the already-fixed expanded view (#7764)
- fix(db): log the fatal boot-time SQLite driver-cascade failure before it propagates instead of swallowing it silently, so `app.log` captures the real cause on Termux/Android (#7773)
- **fix(sse):** Claude-Code-compatible bridge (AgentRouter and any CC provider) no longer loses vision input from OpenAI-format clients (OpenCode/Kilo/Cline): `image_url`, AI-SDK `image` and Chat Completions `file` parts are now converted to Claude `image`/`document` blocks before dispatch, and media-only user turns are no longer dropped by the empty-content filter. (thanks @beingshafin) ([#7777](https://github.com/diegosouzapw/OmniRoute/issues/7777))
- **perf(db):**`getProviderConnections` now reads from a shared `rawConnectionsCache` (500-entry LRU, 5s TTL) and returns lazy-decrypting proxies — encrypted credential fields (`apiKey`, `accessToken`, `refreshToken`, `idToken`) are only AES-GCM decrypted on first property access instead of eagerly for every cached row. `getCachedProviderConnections` now delegates through this single raw cache instead of maintaining its own, and `deleteProviderConnectionsByProvider` gained a missing `invalidateDbCache` call. ([#7787](https://github.com/diegosouzapw/OmniRoute/pull/7787) — thanks @oyi77)
- fix(docs): document CREDENTIAL_REDACTION_ENABLED and GHE_COPILOT_OAUTH_CLIENT_ID in .env.example/ENVIRONMENT.md, closing the env-doc-sync gate drift (#7793)
- fix(combo): retry intermediate pipeline-strategy steps on transient upstream errors (429/502/503/504), gated by `combo.config.maxRetries`/`retryDelayMs`, without retrying terminal auth/request errors (#7794 — thanks @AndrianBalanescu)
- **fix(runtime):** Apply refreshed request-queue settings, restore inter-batch health-check jitter, and invalidate database read caches when the SQLite singleton resets; refresh stale CI baselines and fixtures exposed by clean release runs ([#7812](https://github.com/diegosouzapw/OmniRoute/pull/7812) by [@backryun](https://github.com/backryun)).
- fix(oauth): attempt Antigravity onboarding inline when `projectId` is empty, fixing a regression from #5193 that re-introduced the #2541 catch-22 for new accounts (#7814)
- fix(api): add amazon-q to the static model catalog so "Available Models" no longer 400s (#7820)
- fix(sse): preserve parallel_tool_calls for GPT-5.6 ultra/max delegation under Codex Responses Lite (#7821)
- fix(quality): tolerate ESLint's trailing unpruned-suppressions stderr text in validate-release-green (#7837)
- fix(test): widen ratelimit-admission pollUntil deadline to 10s to end nightly-compat flake (#7842)
- **fix(cli):** Windows `omniroute dashboard` browser fallback uses `rundll32` instead of `cmd.exe` to open the dashboard URL, avoiding an unnecessary shell spawn ([#7844](https://github.com/diegosouzapw/OmniRoute/pull/7842))
- fix(dashboard): Request Logs detail modal no longer crashes when the persisted error is a structured object (#7845)
- fix(backend): stop `buildClientRawRequest` deep-cloning the whole request body on every chat request (#7847) — every consumer of `clientRawRequest.body` is observability and keeps at most a bounded copy, so the unbounded clone retained ~41x more than anything used it (3.19 MiB vs 0.08 MiB on a 3.05 MiB / 729-message agent request). Also makes `cloneBoundedForLog` idempotent: arrays, objects and strings all exceeded their own bounds once the truncation marker was added, so re-bounding an already bounded payload silently dropped a further item and misreported the original length
- fix(sse): copy the combo attempt body shallowly instead of deep-cloning it per target (#7847) — the deep clone cost 9.53 MiB at 3 targets and scaled linearly with the target count (31.78 MiB at 10) on a 3.05 MiB agent request, while the isolation it provided only ever needed to contain top-level scalar writes. Also fixes a real cross-target leak in round-robin, which copied the body only when the reasoning buffer changed `max_tokens` and otherwise shared the caller's object, so a Background Task Redirection on one target rewrote `body.model` for the next
- fix(sse): estimate the combo fallback-compression trigger from the request object instead of `JSON.stringify(...)` (#7847) — the string path charged an inline base64 image as if every character were prose (~50k tokens instead of ~1.2k on a 200 KB image), falsely tripping compression on requests nowhere near the context window; the same over-count #8368/#8401 fixed elsewhere. Adds `jsonLength()`, an exact serialized-length walker (property-tested against `JSON.stringify`), and uses it for the readiness-timeout and token estimates so a multi-megabyte body is no longer materialized as a string just to be measured
- fix(providers): Copilot reasoning_text now surfaces in Claude-format non-stream responses, fixing spurious 502s (#7856)
- fix(providers): treat a non-401/403 response from the web-cookie `/models` validation probe (redirect, login-HTML 200, 404, 405, 429) as `unsupported` instead of `valid: true` for providers whose registry `baseUrl` is a conversation/completion endpoint rather than a real API root (#7857)
- fix(providers): copilot-m365-web now fails loudly on a fully-empty turn instead of a silent `stop`, and the enterprise tier threads its own `optionsSets`/`tone`/`allowedMessageTypes` into the chat invocation instead of only changing the WS URL (#7858, #7870)
- fix(providers): Gemini Web connection test now accepts the expected 302 redirect instead of failing with 'Redirect blocked' (#7859)
- fix(cli): translate missing better-sqlite3 native bindings error into actionable `omniroute runtime repair` guidance in `reset-password`/setup CLI commands (#7868)
- fix(auth): restore `HEALTHCHECK_BATCH_SIZE` env-var configurability of the OAuth token-health-check sweep batch size, dropped as a hardcoded constant by #7719 (#7875)
- **fix(opencode-plugin):** the official `@omniroute/opencode-plugin` now accepts an optional `managementReadToken` for management-plane catalog GETs while keeping inference requests on the connected `apiKey` — existing configurations continue to fall back to `apiKey`, and MCP token selection remains independent. (thanks @RaviTharuma) ([#7884](https://github.com/diegosouzapw/OmniRoute/issues/7884))
- **fix(dashboard):** Provider Quota cutoff inputs preserve unsaved values across quota refreshes instead of reverting while the operator is typing ([#7889](https://github.com/diegosouzapw/OmniRoute/issues/7889)).
- **fix(sse):** Anthropic requests that reject a completed historical thinking signature now retry once with only that historical thinking omitted, while valid requests and active tool-use cycles remain unchanged. ([#7906](https://github.com/diegosouzapw/OmniRoute/pull/7906))
- **Chat Core**: client aborts that reject the upstream fetch with a raw string reason (e.g. `request_signal_aborted`, `Client disconnected`) are now reported as `499 Request aborted` in the response, request logs, and usage records instead of `FAILED 502 / Bad Gateway`. Follow-up to #7908 for #7907.
- **fix(resilience):** Client-aborted requests are now treated as client lifecycle events rather than provider failures. They no longer cool down the serving connection or feed the provider circuit breaker, so a client that cancels a stream mid-flight (a routine occurrence with Claude Code) can no longer mark healthy accounts unavailable. Genuine upstream failures are still counted. ([#7907](https://github.com/diegosouzapw/OmniRoute/issues/7907))
- fix(cli): spawn the win32 `opencode.cmd` shim with `shell:true` in `omniroute setup opencode --auth` to avoid the Node `spawnSync EINVAL` hardening error (#7913)
- fix(dashboard): correct "Block extra Claude usage" toggle copy to describe the connection-quarantine/fallback behavior it actually performs, instead of the non-existent display-dedup behavior it previously described (en/az/zh-CN/zh-TW/vi/pl) (#7918)
- **fix(autostart):** Windows auto-start no longer flashes a console window — writes a `.vbs` launcher to the Startup folder instead of the `HKCU\Run` registry key, adopting the same approach as 9Router ([#7925](https://github.com/diegosouzapw/OmniRoute/pull/7925))
- **fix(providers):** perplexity-web now detects the `advanced_models_quota_low` upsell surfaced when a stream never materializes answer text, returning HTTP 429 with `reset_seconds`/`Retry-After` instead of a silent empty-content error, and reconstructs plan-goal reasoning from RFC-6902 diff-patched `plan_block` frames (live multi-step streams), not just materialized ones ([#7930](https://github.com/diegosouzapw/OmniRoute/pull/7930)) — thanks @artickc
- fix(sse): parse Gemini 429 RetryInfo.retryDelay / "please retry in Ns" so model lockout honors the short upstream hint instead of falling back to a ~19h until-midnight cooldown (#7940)
- fix(electron): resolve macOS Helper by binary name to remove second inert Dock icon (#7941)
- **fix(providers):** iFlytek Spark (`iflytek`, `sparkdesk`) now target the OpenAI-compatible host `spark-api-open.xf-yun.com/v1`; the previous `spark-api.xf-yun.com` is the WebSocket host and rejects bearer auth ([#7942](https://github.com/diegosouzapw/OmniRoute/pull/7942)) — thanks @FenjuFu
- fix(api): classify `/api/acp/agents` as loopback-only to close an RCE-via-tunnel gap in the ACP custom-agent registry (#7948)
- fix(cli): stop double-prefixing combo model ids in the opencode plugin's static provider catalog (#7976)
- fix(providers): route noauth opencode-zen connections through their assigned proxy ([#7993](https://github.com/diegosouzapw/OmniRoute/issues/7993))
- fix(providers): refresh duckduckgo-web free model catalog to Duck.ai's current wire ids, fixing 400 ERR_BAD_REQUEST (#8000)
- fix(providers): repoint the zai-web executor at chat.z.ai's current v2 chat-completions endpoint, fixing model-independent 404s (#8014)
- fix(sse): bound the Codex SSE peek/passthrough body reads with a per-read timeout so a silently stalled upstream body settles in FETCH_BODY_TIMEOUT_MS instead of hanging ~15min and returning a generic 502 (#8020)
- **fix(providers):** path-shaped multimodal model ids (e.g. `cp/cline-pass/kimi-k3`) resolve native vision via leaf/registry metadata instead of triggering Vision Bridge ([#8032](https://github.com/diegosouzapw/OmniRoute/issues/8032)) — thanks @Prudhvivuda
- fix(cli): merge Node's own bin dir into the CLI healthcheck spawn PATH so npm-shebang tools like codex resolve under a minimal launcher PATH (#8036)
- **fix(sse):** Bound forwarded upstream response headers so large provider metadata cannot turn successful streaming requests into reverse-proxy errors ([#8041](https://github.com/diegosouzapw/OmniRoute/pull/8041)) — thanks @insoln
- fix(db): register SIGHUP handler and stop force-killing the server on win32 stop paths so storage.sqlite's WAL gets checkpointed on shutdown (#8045)
- fix(oauth): warn instead of silently opening an unreachable localhost:1455 redirect for Codex/xAI/Grok CLI OAuth when the dashboard is reached from a LAN IP (#8046)
- **fix(sse):** run the prompt-compression pipeline per turn in the Codex Responses WebSocket bridge instead of skipping it entirely — a reused WebSocket connection now re-runs `prepare()` (auth/policy/memory/reasoning-routing/compression) for every logical `response.create` turn, not just the first (#8052)
- **fix(oauth):** Refreshing the token on an email-less OAuth connection (e.g. GitHub Copilot, whose identity lives under `providerSpecificData`) no longer creates a duplicate connection. `persistOAuthConnection` gated its whole dedup behind `if (tokenData.email)`, so the explicit `connectionId` passed on refresh was discarded and a new connection was created every time; the connectionId is now honored regardless of email, and email dedup only applies when a non-empty email is present. ([#8059](https://github.com/diegosouzapw/OmniRoute/issues/8059))
- fix(routing): anchor quota routing cache on globalThis so cross-chunk reads/writes stay consistent (#8065)
- **fix(providers):** add the missing `poe` REGISTRY entry so built-in Poe model discovery resolves a base URL instead of failing with "No base URL configured for provider" ([#8082](https://github.com/diegosouzapw/OmniRoute/issues/8082))
- fix(translator): set `status: "completed"` on translated OpenAI Responses `input` items so strict Responses-compatible upstreams stop rejecting them with 400 MissingParameter input.status (#8083)
- fix(sse): give keyless Pollinations image requests the same anonymous fingerprint-pool fallback the chat path already uses, instead of a real upstream 401 (#8085)
- fix(sse): stop Codex/Responses sanitizer from turning system/developer image_url parts into invalid output_text (#8089)
- fix(cli): surface the real spawn error (err.code/path/syscall/message) in the process supervisor instead of silently swallowing it on a child spawn failure (#8091)
- **fix(antigravity):** preserve request/tool fidelity, make credits modes explicit, and separate runtime endpoints from discovery/bootstrap ([#8098](https://github.com/diegosouzapw/OmniRoute/pull/8098)) — thanks @nguyenha935
- fix(dashboard): clamp provider-cooldown min/max ms to their field bounds before Save so an out-of-range typed value can never reach the resilience settings API (#8107)
- fix(providers): filter unsupported family-fallback candidates against the provider catalog (#8134)
- fix(providers): classify HTTP 400 model-unavailable as MODEL_NOT_FOUND so Antigravity Pro fallback locks out the deprecated model ([#8136](https://github.com/diegosouzapw/OmniRoute/issues/8136))
- fix(backend): word-boundary-safe tool-result truncation in lite compression mode (#8169)
- fix(api): narrow claudeClassifierCompat auto trigger so stop_sequences alone no longer short-circuits (#8189)
- fix(providers): carve cookie-auth providers out of terminal 401 'expired' classification so one 401 cooldowns instead of killing the connection ([#8200](https://github.com/diegosouzapw/OmniRoute/issues/8200))
- fix(gemini): drop HARM_CATEGORY_CIVIC_INTEGRITY from the default Gemini safety settings (#8231)
- fix(providers): classify per-model-quota 403 and DEGRADED 400 as model-unhealthy in checkFallbackError (#8247, #8248)
- **fix(providers):** reconcile Kimi K3 vision metadata when synced `attachment=false` contradicts image/video `modalities_input`, so `supportsVision`, `attachment`, and exposed modalities agree ([#8250](https://github.com/diegosouzapw/OmniRoute/issues/8250)) — thanks @Prudhvivuda
- **fix(providers):** the Providers page Learn more button now opens the maintained GitHub documentation instead of the retired `docs.omniroute.io` page ([#8284](https://github.com/diegosouzapw/OmniRoute/pull/8284)) - thanks @KaynXu
- fix(api): fold namespace into the flattened Chat tool name so cross-namespace leaves do not collide ([#8295](https://github.com/diegosouzapw/OmniRoute/issues/8295))
- **fix(runtime):** sanitize Muse Spark fetch failures before logging and resolve the sql.js WASM asset without the unexported package metadata subpath ([#8298](https://github.com/diegosouzapw/OmniRoute/pull/8298)) — thanks @backryun
- **fix(sse):** Combo middleware preserves OpenAI Responses request bodies and maps combo system overrides to `instructions`, preventing Responses-native fallbacks from receiving an unsupported `messages` parameter. ([#8310](https://github.com/diegosouzapw/OmniRoute/pull/8310)) — thanks @ridho9
- fix(api): accept the current compatible-provider connection id scheme in the models test route (#8326)
- fix(api): stop leaking the internal provider UUID in /v1/models and honor the configured prefix (#8327)
- fix(dashboard): show custom provider_nodes providers in the Topology view instead of only AI_PROVIDERS (#8328)
- fix(api): stop the 2000-token safety buffer from inflating usage.prompt_tokens in the client response (#8331)
- fix(backend): keep combo routing from dispatching image requests to text-only targets (#8332)
- fix(sse): strip third-party-agent signals from the Hermes system prompt that trigger Anthropic 400 extra-usage (#8350)
- **fix(i18n):** Restore brand/model proper nouns (Claude, OpenAI, Anthropic, Gemini, MiniMax, etc.) in zh-CN and zh-TW — replace Chinese phonetic/translation forms (克劳德/打开Ai/人择/双子座) with original English, unify "provider" translation to "供应商/供應商", and apply zh-TW localized terminology (網路/設定/檔案/新增/啟用/搜尋/儲存) instead of mainland defaults (#8355 — thanks @ikelvingo).
- fix(api): estimate inline base64 image tokens instead of counting the data URL as text so it does not falsely exceed the context window (#8368)
- fix(backend): stop prompt-cache affinity from silently reordering an explicit priority combo across models (#8370)
- fix(api): accept a missing status query-param on GET /api/plugins instead of rejecting null with Invalid status value (#8374)
- fix(resilience): treat an unreachable-proxy ECONNREFUSED as a circuit-breaker event so combo fails over instead of hitting the 503 max-retry limit (#8376)
- fix(backend): make disabling the global per-key proxy toggle override existing per-key proxy assignments (#8385)
- fix(dashboard): persist compression engine detail settings (Headroom / session dedup / CCR) instead of dropping them on save (#8388)
- fix(plugins): fire registered+active plugin hooks (onRequest/onResponse/onError) during proxying instead of never invoking them (#8395)
- fix(resilience): cap the connection cooldown after a 429 burst so combo fallback is not blacked out past the real rate-limit window (#8396)
- fix(api): reach synced model_capabilities rows for canonical provider ids that only appear as an alias in MODELS_DEV_PROVIDER_MAP, e.g. codex/claude (#8429)
- fix(providers): stop marking a multi-quota-window provider exhausted when only some windows are depleted (LIMIT-200 snapshot eviction drops idle healthy windows) (#8431)
- fix(backend): compute AgentBridge diagnose `dnsConfigured` per-agent instead of hard-coded to Antigravity hosts (#8466)
- **fix(providers):** OpencodeExecutor honors Extra API Keys rotation via `resolveEffectiveKey` (empty primary + extras no longer omit Authorization) ([#8467](https://github.com/diegosouzapw/OmniRoute/issues/8467)) — thanks @Prudhvivuda
- fix(sse): stop combo's "all targets failed" response from attaching one target's retry-after window to an unrelated target's error message (#8486)
- **fix(backend):** capability filters fail closed when every combo target is incompatible, with opt-in `compatFilterFailOpen` ([#8488](https://github.com/diegosouzapw/OmniRoute/issues/8488)) — thanks @Prudhvivuda
- fix(providers): persist a runtime-discovered Antigravity projectId back onto the connection so it survives token refreshes and restarts instead of being rediscovered or lost (#8491)
- fix(cli): `backup create` / `backup auto enable` — option shadowing by the parent `backup` command removed; all flags (`--cloud`, `--encrypt`, `--retention`, `--name`, `--exclude`, `--key-file`) now reach the subcommand handler with their actual values instead of being silently discarded ([#8512](https://github.com/diegosouzapw/OmniRoute/issues/8512))
- **fix(dashboard):** Endpoint/API base URL display honors `OMNIROUTE_BASE_PATH` (e.g. `https://host/omniroute/v1` instead of `https://host/v1` under reverse-proxy subpath deploys) ([#8514](https://github.com/diegosouzapw/OmniRoute/pull/8514)) — thanks @rqzbeh
- **fix(client):** Absolute `fetch("/api/...")` and `EventSource("/api/...")` honor `OMNIROUTE_BASE_PATH` under reverse-proxy subpath deploys (no domain-root 404s for dashboard health/SSE) ([#8515](https://github.com/diegosouzapw/OmniRoute/pull/8515)) — thanks @rqzbeh
- fix(cli): create `~/.cache` (and set `XDG_CACHE_HOME`) before Next.js loads on Android/Termux so `getCacheDirectory()` no longer aborts with `Unsupported platform: android`, which previously left every request as a silent HTTP 500 after the CLI still looked "running" (#8519)
- fix(api): surface a non-blocking warning + startup scan when a combo name shadows a real model id, instead of silently routing with zero signal (#8530)
- **fix(kiro):** support profileless Builder ID quota, preserve CLI auth identity, stabilize social OAuth polling, and use the live model catalog ([#8565](https://github.com/diegosouzapw/OmniRoute/pull/8565)) — thanks @nguyenha935
- fix(test): isolate `context-manager` unit tests on a temp `DATA_DIR` so `getTokenLimit` resolves against the registry fallback instead of the developer's local models.dev sync ([#8568](https://github.com/diegosouzapw/OmniRoute/issues/8568))
- **fix(quality):**`check:file-size --update` now removes baseline entries that already fit the new-file cap even when LOC equals the frozen value (`redundant` path) — closes the #8584 gap where at-cap entries were stuck forever ([#8589](https://github.com/diegosouzapw/OmniRoute/pull/8589)) — thanks @MumuTW
- **fix(sse):** restore Task-Aware Routing config from `settings.taskRouting` at boot and keep it on `globalThis` so multi-graph imports share one store — stops the feature silently reverting to disabled on every restart (#8601) ([#8604](https://github.com/diegosouzapw/OmniRoute/pull/8604)) — thanks @MumuTW
- **fix(sse):** task-aware routing defaults use `auto/*` intents instead of hardcoded provider/model ids (#8602), and the longest-pattern-first fitness table match that landed upstream in 9f5be229b gains a direct regression guard, so rows like `gpt-4o-mini` cannot be shadowed by `gpt-4o` again (#8603) ([#8605](https://github.com/diegosouzapw/OmniRoute/pull/8605)) — thanks @MumuTW
- **fix(providers):** keep registered OpenCode Go effort aliases in the Playground catalog when synced base models are present, and preserve Hunyuan3's 256K context metadata for combo routing ([#8610](https://github.com/diegosouzapw/OmniRoute/pull/8610)).
- **fix(resilience):** report local request-queue expirations as service-unavailable capacity failures instead of upstream 502 errors, preventing incorrect provider cooldowns and same-target retries ([#8613](https://github.com/diegosouzapw/OmniRoute/pull/8613)).
- **fix(docker):** Honor `OMNIROUTE_BASE_PATH` behind reverse-proxy subpaths by baking the subpath into the standalone bundle at image build time (`ARG OMNIROUTE_BASE_PATH`) and patching the published root-path image at container start (`scripts/docker/ensure-docker-base-path.mjs`); healthcheck probes the prefixed `/api/monitoring/health` route ([#8615](https://github.com/diegosouzapw/OmniRoute/pull/8615)) — thanks @DinonowDev
- **fix(resilience):** recover a wedged local request limiter after an idle-capacity queue expiry by retrying the request on a fresh limiter ([#8616](https://github.com/diegosouzapw/OmniRoute/pull/8616)).
- **fix(providers):** Kimi Coding billing-cycle quota errors now recover automatically at the cached reset instead of remaining unavailable ([#8632](https://github.com/diegosouzapw/OmniRoute/pull/8632)) — thanks @glazec
- **fix(oauth):** show GitLab Duo OAuth app / env setup instructions in the Add Connection modal _before_ authorize, using a shared recipe with catalog `authHint` and the authorize-route error fallback (#8688)
- **fix(sse):** combo compression-limit resolution falls back to `ResolvedComboTarget.provider` when `modelStr` lacks a `provider/` prefix, so `getTokenLimit` no longer crashes with `null.toUpperCase()` (#8716) — thanks @DinonowDev
- **fix(db):** fall back to Node's built-in SQLite driver when better-sqlite3 is unavailable, while preserving read-only and existing-file safeguards ([#8724](https://github.com/diegosouzapw/OmniRoute/pull/8724)) — thanks @epsilonode
- **fix(resilience):** keep missing request resources such as Files API ids from triggering model-family fallback, model lockout, or provider/account cooldown (thanks @wilsonicdev) ([#8756](https://github.com/diegosouzapw/OmniRoute/pull/8756))
- **fix(api):**`GET /v1/models` no longer rebuilds the whole catalog on almost every request. The response cache added by [#6408](https://github.com/diegosouzapw/OmniRoute/pull/6408) memoized the body for `modelCatalogCacheTtlMs`, defaulted to 1500 ms — shorter than a single build (~49 s for a 1.3 MB / 2645-model catalog on a real install), so any two requests more than 1.5 s apart both missed the fresh window and the second fell into a stale-while-revalidate rebuild that pins the single-threaded event loop, delaying even the "immediate" stale response. The default is now 60 s, matching the ceiling the settings schema already allows for the override. A short TTL was redundant with the cache's existing invalidation: `invalidateDbCache()` bumps the catalog cache version on every settings/connections/combos/pricing write, so post-write freshness never depended on the TTL. ([#8833](https://github.com/diegosouzapw/OmniRoute/pull/8833))
- **GHE Copilot**: OpenAI-native models now route to `<gheUrl>/responses` instead of failing with `model "..." is not accessible via the /chat/completions endpoint` — `gpt-5.4-mini`, `gpt-5.3-codex`, `mai-code-1-flash` and `oswe-vscode-prime` carry `targetFormat: "openai-responses"`, and `gpt-5.6-sol`/`terra`/`luna` are registered. Claude and Gemini models under `ghe-copilot` keep using `/chat/completions` ([#8835](https://github.com/diegosouzapw/OmniRoute/pull/8835)) — thanks @hppsc1215
- **CLI**: `omniroute launch` no longer truncates pass-through arguments at the first space on Windows — each argument is escaped for cmd.exe (CRT argv rules plus a double caret pass, required because `claude.cmd` re-parses `%*`). A non-zero child exit also stops aborting the process with a libuv assertion: the exit code is set on `process.exitCode` so the loop drains the inherited stdio handles first ([#8837](https://github.com/diegosouzapw/OmniRoute/pull/8837)) — thanks @sumanxg
- **Providers**: Codex GPT-5.6 Sol/Terra/Luna report the current upstream context window of 272000 instead of a stale 372000, so catalog consumers stop compacting past the Codex backend real input limit. Public OpenAI API GPT-5.6 metadata is unchanged ([#8838](https://github.com/diegosouzapw/OmniRoute/pull/8838)) — thanks @TitoTFP
- **Antigravity**: a token refresh now recovers a missing `projectId` instead of leaving the connection stuck on 422 — when the initial OAuth exchange fails to populate it (network timeout, account not yet onboarded to Gemini Code Assist), `refreshCredentials` retries discovery via `ensureAntigravityProjectAssigned` and persists the result, mirroring the runtime `transformRequest` path. Best-effort: a failed discovery leaves the next request to retry ([#8842](https://github.com/diegosouzapw/OmniRoute/pull/8842)) — thanks @HouMinXi
- **CI**: `check:tracked-artifacts` raises the `git ls-files` stdout ceiling to 64 MiB — the default 1 MiB `execFileSync` buffer had only ~6 KB of headroom against the tracked tree, and overflowing it throws ENOBUFS in the pre-commit hook, breaking `git commit` for everyone working the repo ([#8844](https://github.com/diegosouzapw/OmniRoute/pull/8844))
- **OAuth**: `ANTIGRAVITY_OAUTH_CLIENT_TYPE=web` lets a remote deployment use a Google Web application OAuth client, upgrading the loopback redirect to `OMNIROUTE_PUBLIC_BASE_URL` instead of leaving the operator to hand-edit the callback URL. Opt-in and requires custom credentials — unset or `desktop` keeps the existing loopback behaviour untouched ([#8845](https://github.com/diegosouzapw/OmniRoute/pull/8845)) — thanks @HouMinXi
- **fix(executors):** Vertex AI now routes current-generation Claude models (`claude-sonnet-4-6`, `claude-haiku-4-5`, etc.) through the partner endpoint instead of misrouting them as Google-publisher models ([#8852](https://github.com/diegosouzapw/OmniRoute/pull/8852)) — thanks @wgordon17
- **CLI**: `omniroute launch-codex` no longer corrupts the codex argv on Windows — every invocation was affected, not just multi-word ones, because `buildCodexProviderArgs()` injects `-c` flags whose TOML values are quoted and cmd.exe stripped those quotes. The escaping helper introduced for `launch` is now shared from `bin/cli/utils/winShellArgs.mjs` and applied to the whole codex argv. A non-zero child exit also sets `process.exitCode` instead of calling `process.exit()`, which aborted with a libuv assertion while the inherited stdio handles were still closing ([#8856](https://github.com/diegosouzapw/OmniRoute/pull/8856)) — thanks @sumanxg
- **Providers**: AGY model sync now discovers newly announced chat models from the live Antigravity catalog while filtering internal and tab-completion-only models, with the bundled catalog retained as an offline fallback
- fix(security): bound JWT-extraction regexes in adobeFireflyClient to prevent polynomial ReDoS (CodeQL #754/#755/#756)
- Fixed every non-streaming Codex (ChatGPT-account) chat request failing with `[502]: Response body is already used (reset after 1m)`: `peekCodexSseTransientError` re-acquired a reader on the upstream `response.body` after `releaseLock()` to continue draining it, which throws on undici. It now keeps the single reader it already holds. The thrown TypeError was also being mis-classified as a 60s rate limit (cooldown + circuit breaker) — that misfire disappears with the double-read fixed.
- **Logging**: a broken stdout/stderr pipe no longer drives a self-sustaining log-flood loop — `consoleInterceptor` attaches `'error'` listeners to both std streams so an async EPIPE stops becoming an `uncaughtException` the framework re-logs through the patched console (measured 3,387 uncaught exceptions in 1.5s, now 0), non-EPIPE stream errors are re-raised so conditions like `ENOSPC` stay fatal, `structuredLogger`'s raw stderr writes skip a stream already known dead, interceptor disk writes are rate limited at `error` level only so ordinary logging is untouched, and a log directory removed at runtime is recreated instead of silently ending file logging (#8181)
- docs: correct three stale references caught by the fabricated-docs gate (`/api/version` → `/api/monitoring/health`, migration `125_` → `126_reasoning_routing_rules.sql`)
- Fix the Turbopack `next build` breaking with "Module not found: Can't resolve 'child_process'": the Kimi sponsor banner's client-side version gate imported semver helpers from `versionCheck.ts` (a server module with a top-level `child_process` import), dragging Node built-ins into the browser bundle. The pure `isNewer`/`normalizeVersion` helpers now live in a dependency-free `versionCompare.ts`; `versionCheck.ts` re-exports them for back-compat.
- Fix `mitm-dnsConfig` unit tests failing when the suite runs as root (or with `OMNIROUTE_NO_SUDO` / no `sudo` installed): they hardcoded `sudo` as the spawned command, but `resolveSudoSpawn()` deliberately strips the `sudo -S` prefix in those environments (#6122). The tests now assert the effective invocation (`tee -a <hosts file>`, elevated or not).
- muse-spark-web: the WebSocket 401 auth-failure message now names the live `ecto_1_sess` cookie (the retired `abra_sess` name was dropped in the GraphQL→WS migration), so users know which cookie to re-paste.
- Harden the Notion web provider thread-session cache against cross-tenant access: the cache is now namespaced per caller (hash of the caller's cookie) instead of only by Notion space id, and client-supplied thread ids are validated as real Notion UUIDs before use — closing an IDOR where two users of the same Notion space could pin/read each other's thread (#7900 security review).
- fix(security): OIDC login gate now requires `email_verified` before honoring the email claim against the allowlist (an unverified IdP email matching an allowlisted address no longer bypasses the gate), and error redirects resolve against the parsed request URL instead of the raw Host header
- **Build**: the packed tarball boots again — #7191's `../../src/…runtimeTimeouts.ts` import in `standalone-server-ws.mjs` escaped the package after the dist-root copy (`ERR_MODULE_NOT_FOUND` on every boot, #7065 class, caught live by the new `check:pack-boot` gate); the helper now lives in the shipped sibling `main-server-timeouts.mjs` (parity-tested against the canonical TS implementation) and the closure test bans package-escaping `../` imports in npm-shipped wrappers
- **Packaging**: new `check:pack-boot` gate packs the real npm tarball, installs it into a clean prefix (postinstall runs for real) and boots the installed CLI until `/api/monitoring/health` returns 200 with the packed version — the runtime gate that structure checks could not provide (3 releases shipped boot-crashing tarballs with green packaging lists: tls-options/3.8.41, #7040, #7065). Wired into the CI `package-artifact` job and `check:release-green --with-build`
- **Packaging**: pack-artifact closure tests now cover EVERY npm-shipped dist wrapper (derived from `EXTRA_MODULE_ENTRIES`) and the `bin/omniroute.mjs` CLI entry — including dynamic `import()` and `require()` forms the original server-ws test missed — requiring each local import in both the prepublish prune allowlist and `check:pack-artifact`; `bin/cli/data-dir.mjs` and `bin/cli/utils/storageKeyProvision.mjs` are now required tarball paths (#7065 class hardening, WS1.1)
- **fix(cli):** CLI detection now refreshes stale cached results, reports discovered versions, and checks the Continue `cn` binary instead of assuming it is installed.
- **fix(docker):** Podman deployment guidance now distinguishes local Linux rootless engines from macOS/Windows Podman Machine, corrects Quadlet and local-build instructions, and avoids unsupported remote-client `podman unshare` hints ([#8497](https://github.com/diegosouzapw/OmniRoute/issues/8497)).
- **fix(ui):** Theme React Flow controls correctly in dark mode, improve idle connector contrast, and localize the provider topology legend.
- **fix(plugins):** the generated plugin host script is now deleted synchronously — an async `rm()` lost the race against process exit, so every plugin load under `node --test --test-force-exit` leaked one temp `.mjs` into the OS temp dir; two plugin manager tests also stopped leaking child processes
- **fix(compression):** the Headroom SmartCrusher tabular-compaction guard now also skips `role: "developer"` messages, not just `role: "system"` — Codex CLI sends its instructions/tool-schema turn as `developer` (the Responses-API equivalent of `system`), so an embedded JSON array (e.g. an `update_plan` example) could get tabular-compacted, corrupting the model's tool-calling instructions and breaking Codex CLI plan mode. (thanks @SingCJ)
- **Skills**: register `cli-skill-collector` in the agent-skills catalog (types union, curated entry, CLI id list) — #6294 shipped the `skills/cli-skill-collector/` directory without the catalog registration, so it was unreachable via the API and Integration CI failed on the catalog-integrity test; counts aligned (44 API+CLI, 45 with config)
- **fix(responses):** Make Responses API to Chat Completions downgrades provider-neutral and loss-aware. Tools declared through `additional_tools` are preserved, structured output and `allowed_tools` are translated, refusal history is normalized, unsupported input item types fail explicitly instead of disappearing, and Responses-only execution/cache fields no longer leak to strict Chat endpoints.
- **fix(stream):** Responses API clients (`/v1/responses`) no longer receive a stray `</think>` text delta at the start of the assistant message on Claude-format upstreams (observed with kimi-coding). The `</think>` close marker (#4633) exists for Chat Completions clients that scan content for it; Responses API clients receive reasoning as structured reasoning items, so the marker is now always suppressed on that path — including the GLM and zed-hosted executors, which do their own Claude→OpenAI translation.
- fix(security): bump adm-zip to ^0.6.0 (dev transitive via promptfoo/onnxruntime-node) to clear the crafted-ZIP 4GB-allocation advisory, and tighten mitm DNS test host assertions to exact/suffix matching (CodeQL js/incomplete-url-substring-sanitization)
- fix(quality): register the two covering unit tests missing from stryker tap.testFiles (#6846, #7333)
- fix(auth): restore the TICK_MS constant dropped by #7719, which made initTokenHealthCheck() throw a ReferenceError at startup
- fix(guardrails/chat): do not whole-request-reroute Vision Bridge away from credentialed models (e.g. combo target zai/glm-5.2 or grok-cli → opencode-zen noauth 401); align body.model with X-Route-Model so post-guardrail cannot undo the routing header
- **Providers**: yuanbao-web no longer forwards a foreign single cookie pair upstream — `buildYuanbaoCookie` only trusts `hy_user`/`hy_token` extractions the input explicitly names, so a missing session token now fails fast with the local 401 guidance instead of a live Tencent round-trip
@@ -620,6 +1118,106 @@ _Living section — regenerated 2026-07-19 from all 306 cycle commits (bump 2c62
- **fix(base-red):** full-suite realignment after the 102-PR merge campaign: two real production fixes (legacy `refresh_token` column healed before its index is created; `shouldSkipCloudSyncInitialization` no longer swaps its `(env, argv)` arguments) plus 13 test files, goldens, provider counts, and env docs realigned to the live-validated behavior of the merged PRs.
### 📝 Maintenance
- **Merge-train script** (`scripts/release/merge-train.sh`): batch-validates N queued PRs as ONE merged result on the runner box — merges every queued PR into a throwaway worktree cut from the release tip, runs the fast-gates parity suite once, and prints the `--admin` evidence block per PR (merge-gates §7). Replaces O(N²) per-PR CI re-runs in merge-storms. Regression guard: `tests/unit/merge-train-plan.test.ts`. ([#6784](https://github.com/diegosouzapw/OmniRoute/pull/6784))
- **release:**`list-uncovered-commits.mjs` now unions the CHANGELOG scan window with `changelog.d/` fragment refs (filename `<PR>-` prefix + every `#N` in the body), so a commit covered only by a fragment is no longer reported as an uncovered reconciliation gap ([#6857](https://github.com/diegosouzapw/OmniRoute/issues/6857) via [#6878](https://github.com/diegosouzapw/OmniRoute/pull/6878))
- **chore(ci):** stop dependabot from proposing `typescript` majors — `typescript-eslint` pins a hard peer upper bound (`>=4.8.4 <6.1.0`), so a TS 7 bump violates the peer and takes the whole toolchain red at once. #7068 grouped it with 6 harmless dev bumps and blocked all of them. TS majors now migrate intentionally, in their own PR.
- **maintenance(quality):** re-baseline `file-size` for two legitimately-grown files from the v3.8.49 owner-PR campaign — `src/app/api/usage/analytics/route.ts` 942→948 (180d/365d range cases, #7213) and `tests/unit/audio-transcription-handler.test.ts` added to `testFrozen` at 824 (Gladia STT test cases, #7603). Fast-gates PR→release skip `check:file-size`, so this only surfaced on re-sync; no behavior change.
- **fix(i18n):** backfill the four `usage.*` quota-visibility keys #7251 added to en.json into vi.json (hideQuotaRow, showQuotaRow, hiddenQuotaRowsLabel, quotaVisibilityUpdateFailed) — their absence left `__MISSING__` markers that failed `i18n-vi-completeness` on the release tip for every fresh PR run.
- **docs(codex):** document session affinity (`sessionAffinityTtlMs`) and stream idle timeout (`STREAM_IDLE_TIMEOUT_MS`) for multi-hour Codex CLI tasks, with a concrete recommended config, diagnosability string, and an explicit "defaults stay opt-in" decision — mirrored in `skills/config-codex-cli` (#7287)
- **test(dashboard):** restore dedicated regression coverage for #6815's `QuotaCardGrid` multi-column density guarantee, decoupled from the specific Tailwind token so it survives the #7027 auto-fit migration ([#7291](https://github.com/diegosouzapw/OmniRoute/pull/7291))
- **Antivirus false-positive note** (`docs/guides/TROUBLESHOOTING.md`): documents why Avast/AVG quarantine the packaged `README.md` with `MD:HttpRequest-inf[Susp]` — a heuristic false positive on the ~15 `http://localhost:20128` examples the file ships with (via `package.json` → `files`). Covers how to stop the notifications, how to report the false positive upstream, and why the localhost examples are deliberately left alone. (#7295 — reported by @DemonNCoding, #5946)
- **chore(release):** add `scripts/release/rehome-open-prs.mjs` — the Phase 0a.0b PR re-home, scripted with a read-back after every retarget. `gh pr edit --base` exits 0 without applying (v3.8.42), `gh pr list` silently caps at 30, and the v3.8.49 freeze had 148 open PRs to move — none of which a hand-run loop survives reliably. ([#7307](https://github.com/diegosouzapw/OmniRoute/issues/7307))
- Add an advisory production build to the PR-to-release quality workflow for non-draft code
changes and Mergify queue branches, mirroring the Node 24 Turbopack build path from full CI.
- **docs:** Add `docs/INCIDENT_RESPONSE.md` — a non-security incident-response runbook (severity ladder, first-15-minutes checklist, and per-failure-mode mitigation steps for provider outages, latency regressions, and auth/data-layer incidents) ([#7334](https://github.com/diegosouzapw/OmniRoute/pull/7334)) — thanks @KooshaPari
- **README tier-cascade diagram animated** (`docs/diagrams/tier-cascade.svg`): the ASCII 4-tier auto-fallback block in the README is now a self-contained animated SVG (SMIL-only, 16 KB, 16s loop in 4 acts — quota-out/budget-hit hand-offs down to the always-on free tier) that plays inside GitHub's `<img>` sandbox; full flow preserved in the img alt text, hand-authored-diagrams section added to `docs/diagrams/README.md` (#7615).
- **Docs provider-count sync** (`README.md`, `AGENTS.md`, `CLAUDE.md`): provider-count mentions bumped 253 → 259 to match the auto-generated catalog, un-blocking the strict `check:docs-counts` gate that had started failing on every PR targeting the release branch (#7616).
- **README animated diagrams** (`docs/diagrams/pool-fair-share.svg`, `docs/diagrams/combo-always-on.svg`): the last two ASCII blocks in the root README — the `"team-codex"` key-pool fair-share example and the `"always-on"` priority combo — are now hand-authored SMIL SVG animations (16s loops, DESIGN_SYSTEM.md palette, GitHub `<img>`-sandbox safe), completing the set started by tier-cascade.svg (#7615). Full block copy preserved verbatim in the `img` alt text; both registered in `docs/diagrams/README.md`.
- **docs(ops):** publish public branching/release model (`release/*` = active cycle, `main` = published line, tag = ship marker) ([#7627](https://github.com/diegosouzapw/OmniRoute/issues/7627)) — thanks @c4usal
- **README animated diagrams, round 3** (`docs/diagrams/cli-terminal.svg`, `docs/diagrams/compression-pipeline.svg`): the CLI subcommand list becomes a mini animated terminal (3 real commands typed and answered with formats copied from the actual `bin/cli` printers + a scrolling 30-subcommand ticker), and the compression flow line becomes an animated 10-engine funnel (10,000→~1,080 tok, RTK → Caveman stack highlighted, code token always preserved). SMIL-only, DESIGN_SYSTEM.md palette, GitHub `<img>`-sandbox safe — completing the set from #7615/#7626. Both registered in `docs/diagrams/README.md`.
- README: replaced the free-tier budget preview mockup with a single detailed animated SMIL card (`docs/diagrams/free-tier-budget.svg`) — ~1.6B/mo hero, honest-math panel (15 providers ToS-flagged), 21-pool budget bar, full per-model grid, ~616M signup-credit chips, un-countable providers + $10 OpenRouter top-up, live footer (#7665)
- README: standardized all 28 tables to the same full content width — 13 tables received a calibrated invisible header spacer (`docs/screenshots/spacer.svg`), 15 already rendered full-width naturally; zero data cells changed (#7666)
- Realigned 13 test files that had drifted red on the release tip after the 102-PR merge campaign (full-suite sweep): provider counts/goldens/docs regenerated, 1M-beta assertions caught up with the live-validated behavior, and two real fixes — legacy `refresh_token` column healed before its index is created, and `shouldSkipCloudSyncInitialization` no longer swaps its `(env, argv)` arguments. ([#7690](https://github.com/diegosouzapw/OmniRoute/issues/7690))
- README: unified animated card system — numbers audited against the v3.8.49 tree (268 providers, 104 MCP tools, 25k+ tests, 26 CLIs, 40+ free-forever, 43 locales, regenerated provider reference); one flat style contract across all cards; 5 new SMIL cards (hero fused with the budget card, "Why" 10-row pain-vs-fix ledger, 18-strategy flow grid, "Private & Local-First" 11-row guarantee ledger, 3-layer resilience card replacing the always-on combo card) plus a rebuilt compact half-height CLI terminal; every animation is pause-at-t0-safe — the first frame is always the finished composition (includes the budget-bar freeze fix on the shipped free-tier card) (#7769)
- **chore(quality):** fix two release-tip base-reds blocking every fresh PR run: rename `providerTierField.ts` → `providerTierFieldApi.ts` (case-only filename collision with `ProviderTierField.tsx` from #7838 — breaks webpack on case-insensitive filesystems, caught by the #6584 guard) and register `combo-skip-conn-disable-plugin-block-7806.test.ts` (from merged #7806) in stryker `tap.testFiles`.
- **fix(i18n):** backfill the six `providers.tierOverride*` keys #7838 added to en.json into vi.json (translated with the locale's existing tier vocabulary) — their absence failed `i18n-vi-completeness` (key parity + both ICU checks) on the release tip for every fresh PR run.
- **chore(quality):** regenerate the provider translate-path golden snapshot for the navy/aihorde entries and the liquid inference URL added by #7840 — the missing regen left the golden gate (Unit shard 4/4) red on the release tip for every fresh PR run.
- **chore(quality):** prune a stale ESLint suppression for `tests/unit/db-migration-runner-account-identity.test.ts` (2 `no-explicit-any` entries that no longer occur after #7843 reorganized that test) — the leftover suppression failed `lint:json --max-warnings 0` (exit 2, "suppressions left that do not occur anymore") on the release tip for every fresh PR run.
- Extract `resolveHeadroomDetail()` from the compression-preview `dispatchCompression` dispatcher so it stays under the cognitive-complexity gate after the #8056 headroom-minRows threading (pure refactor, no behavior change).
- **chore(quality):** rebaseline complexity (2130→2183) and cognitive-complexity (951→968) across the v3.8.49 `/merge-prs` queue-drain. The first step (→2169/→956) cleared inherited base drift measured on the pristine release tip (the PR→release fast-path never ratchets these). The second step (→2183/→968) absorbs the aggregate own-growth of the 41-PR merge-train (each PR under-ceiling individually; the combined batch adds +14/+12). Owner-approved (2026-07-25); structural shrink tracked in [#3501](https://github.com/diegosouzapw/OmniRoute/issues/3501). ([#8566](https://github.com/diegosouzapw/OmniRoute/pull/8566)) — thanks @MisileLab
- **refactor(sse):** extract combo dispatch prelude (pin/fusion/pipeline/runtime-unit short-circuits) into `combo/dispatchPrelude.ts` — pure move, no behaviour change; #3501 god-file decomposition ([#8582](https://github.com/diegosouzapw/OmniRoute/pull/8582)) — thanks @MumuTW
- **chore(quality):** bank completed file-size shrinks into `file-size-baseline.json` via shrink-only `--update` on current tip (no raised caps) ([#8585](https://github.com/diegosouzapw/OmniRoute/pull/8585)) — thanks @MumuTW
- **refactor(sse):** extract combo target resolution into `combo/targetResolution.ts` (`resolveComboTargetPipeline`) — pure move, no behaviour change; #3501 PR 2 of ~7 ([#8592](https://github.com/diegosouzapw/OmniRoute/pull/8592)) — thanks @MumuTW
- **Source-scanner negative guards** (`tests/unit/source-scanner-guards.test.ts`): hard gate (no baseline, no allowlist) that fails any test variable holding project source read as text whose usages are _all_ negative guards (`assert.doesNotMatch` / `.includes(…) === false`). Those assertions pass against an empty string, so extracting the guarded code into a new file keeps them green while they protect nothing — silently deleting regression coverage during the file-splitting campaign. Classification runs on logical statements with strings, regexes, and comments blanked out, so a guard wrapped across lines cannot slip past. Fixes the 7 existing violations across 6 files with one positive anchor each; two are security scope guards that were held only by multi-line negative assertions — the SSRF guards on `/api/sync/initialize` (`providers-autosync-ssrf-323`) and the proxy-bypass guards on `chatHelpers.ts` / `chatCore.ts` (`proxy-bypass-scope-guard-3226`), the latter pinned to `handleChatCore` precisely because that file is a decomposition target. Adds `tests/_helpers/readSrc.ts`, a repo-root-relative reader that throws on a missing or empty file instead of returning `""`. Regression guards: two synthetic multi-line cases in the gate's own suite. First step of the god-file decomposition campaign tracked in #8617. ([#8619](https://github.com/diegosouzapw/OmniRoute/pull/8619)) — thanks @MumuTW
- **chore(skills):** regenerate `skills/cli-backup-sync/SKILL.md` so it matches the agent-skills catalog — drops stale `backup status` flags the generator no longer emits; unblocks Merge integrity (`check:agent-skills-sync`) for every PR against `release/v3.8.49` ([#8657](https://github.com/diegosouzapw/OmniRoute/pull/8657)) — thanks @MumuTW
- chore(quality): extract pure provider input parsers to a leaf module so the agentrouter persist test no longer drags the UI import graph (@lobehub/icons ESM build crashed Node 24's CJS require in the CI unit shard)
- Realign the Antigravity non-streaming 429-retry test with the executor's actual contract: it returns the already-converted OpenAI `chat.completion` payload, so the test asserts that payload directly instead of re-parsing it as raw SSE (which returned `null` and failed the release-tip unit suite).
- chore(quality): restore no-explicit-any severity to error (silently downgraded by #6786, broke 439 bulk suppressions), fix 2 unsuppressed anys in repro-6912 test, allowlist migration gap 121, freeze-bump stream.ts/ProxyRegistryManager/tokenHealthCheck (combined-merge drift)
- chore(base): pt-BR/en i18n keys for #6909 relay-repair/free-pool UI + align Cloud Code Gemini defaults test with the #6943 non-thinking-model contract
- chore(base): re-export relayProbeStats from localDb (db-rules gate, #6909 follow-up) and document OMNI_MAX_CONCURRENT_CONNECTIONS in .env.example/ENVIRONMENT.md (env-doc gate, #6590 follow-up)
- fix(tests): realign the disabled-compression combo integration test with the #7379 pre-dispatch context-window boundary (body resized into the 70%-threshold↔window corridor) and add an integration lock (`chatcore-context-window-boundary`) asserting over-window requests are rejected with 400 `context_length_exceeded` before any upstream fetch
- CI: `pr-test-policy` fetches the base branch with full history instead of `--depth=1` — the shallow graft made `merge-base` resolve wrong for PR branches that recently merged the release, so the three-dot diff blamed the PR for OTHER merged PRs' changes (false "deleted test"/"weakened asserts" reds; observed on #7329 being blamed for #7106's files).
- **ci:** unit fast-path sharding doubled 2→4 (halves the heaviest job's wall time) (#6781); the 3 heaviest fast-path jobs can route to the self-hosted VPS runner pool behind `USE_VPS_RUNNER` (#6691); `VPS_ALWAYS_ON` keeps the dedicated 24/7 CI host up across releases (teardown becomes a no-op) (#6693).
- **CI**: Codecov patch-coverage on every PR diff (informational during calibration — `codecov.yml` sets nothing blocking; strict-patch/lenient-project philosophy on top of the existing 60% c8 floor + ratchet); the CI coverage job now actually emits `coverage/lcov.info` (the `lcov` reporter was missing, so the artifact silently skipped it — the same file Sonar consumes)
- Classify upstream dispatches by host instead of URL path shape in the combo-routing integration harness, and close the `auto` test pool to its seeded connections. The path heuristic mislabelled every OpenAI-compatible upstream as `openai` and stopped recognising OpenAI's own `/v1/responses` calls, which made `combo-matrix/auto.test.ts` fail on a release tip whose routing was in fact correct.
- **chore(combo):** extract 8 pure error predicates and quota status helpers (`clampPercent`, `quotaRemainingPercentFromQuota`, `normalizeConnectionStatus`, `hasFutureRateLimitUntil`, `getConnectionStatusQuotaCutoffReason`, `isContextOverflow400`, `isParamValidation400`, `isModelScoped400`) from `open-sse/services/combo.ts` into `open-sse/services/combo/comboPredicates.ts` — pure move, zero behavior change; `combo.ts` shrinks from 3,651 to 3,554 lines while maintaining backward-compatible re-exports.
- **CI**: raise the Coverage job timeout 10→20min — the lcov reporter added for Codecov/Sonar (#7114) pushed the 8-shard report merge past the old cap, and three release-tip runs died at exactly 10min as job-timeout "cancelled"
- **CI**: raise the dast-smoke job timeout 12→25min — the CLI bundle build alone varies 6-11min on GitHub-hosted runners, so the old cap killed Schemathesis mid-run (3 consecutive false-negative timeouts on 2026-07-14)
- chore(validation): decompose `src/lib/providers/validation.ts` (→ 442 lines) by extracting the web-cookie, kiro and specialty inline validators into `validation/*` leaves — behavior-preserving move; the specialty validators that captured `isLocal` from the enclosing closure now take it as an explicit parameter, with the host dispatcher passing it at each call site
- chore(token-refresh): decompose `open-sse/services/tokenRefresh.ts` (999 → 724 lines) by extracting the rotation-map, CAS guard and circuit-breaker refresh logic into `tokenRefresh/*` leaves — behavior-preserving move; `tokenRefresh.ts` still re-exports the moved symbols so the public surface is unchanged
- chore(usage): decompose `open-sse/services/usage.ts` (999 → 253 lines) by extracting the crof, nanogpt, qoder, opencode, deepseek, bailian, vertex, xiaomi-mimo, xai and github usage fetchers into `usage/*` leaves — behavior-preserving move, the file is now a thin provider→fetcher dispatcher and the public import surface is unchanged
- **docs:** routing-strategy count reconciled to 18 across AUTO-COMBO.md, README and AGENTS.md, and `p2c` casing fixed to match `ROUTING_STRATEGY_VALUES` (#6643, #6644, #6646 — thanks @chirag127); CLAUDE.md updated with the renamed review/triage/implement skill-family names (#6663).
- **CI**: E2E matrix shards are now duration-balanced (LPT bin-packing over `config/quality/e2e-timings.json`) instead of Playwright's count-based `--shard` — measured skew was 14× (24m47s vs 1m47s), making E2E the CI critical path; the balancer self-verifies that every spec lands in exactly one shard and falls back to plain `--shard` on any inconsistency
- **CI**: the new Electron Windows prepare-bundle leg (WS1.5) is advisory while it matures — its first real run failed with the error swallowed by pwsh; the step now runs under bash (stderr captured) with `continue-on-error`, tracked for promotion once green
- **CI**: the Electron Package Smoke job now runs a Windows leg that executes `prepare:bundle` (the native ABI rebuild + spawn plan) per release PR — the v3.8.48 Windows bug (`npx.cmd` spawned without shell) could previously only surface on the release tag, its first-ever execution
- **Quality gates hygiene (WS6/D3 + WS1.7)**: gitleaks baseline zeroed — the 3 frozen `generic-api-key` false positives (latency field names + the public Anthropic beta-header value) are allowlisted with justification, so any NEW secret finding now regresses the ratchet from 0; the orphaned `semgrepFindings` baseline metric was dropped (never wired to a gate; CodeQL covers the OWASP families); Dockerfile now has a hadolint gate in the lint job (digest-pinned, error-threshold — the 5 pre-existing warnings stay visible without blocking)
- fix(oauth): register `ghe-copilot` in the OAuth provider id map and the client-safe MITM host mirror, which #7546 left out of both — the gap turned the whole release branch red on `oauth-providers-config` and `mitm-tool-hosts`
- fix(dashboard): add the ghe-copilot entry to the traffic-inspector agent map and type `GheConfigStep`'s `error` prop as a renderable string, clearing the two dashboard-typecheck regressions #7546 left behind
- chore(perf): add `npm run bench:heap-body` — a deterministic benchmark that attributes retained V8 heap to each request-body copy on the chat path (entry log clone, combo per-target clone, token-estimation string), reproducing the #7847 incident shape (3.05 MiB / 729 messages / 86 tools) so the clone-amplification work can be justified and regression-guarded with numbers instead of intuition
- **CI**: hotfix fast-lane — PRs labeled `hotfix` (owner-applied, production-broken only; entry policy in `docs/ops/RELEASE_CHECKLIST.md`) skip the 9-shard E2E matrix, coverage ratchet and extended gates while keeping build, unit/integration/vitest, lint/typecheck and the tarball boot-smoke (~15min instead of ~33min); tests-only diffs outside `tests/e2e/` skip the E2E matrix automatically via the new `testsOnly` change-classification output
- chore(ci): resync the stale `no-explicit-any` suppression count for `tests/unit/proxy-registry.test.ts` (frozen at 55, actual 54 since #8447) — ESLint was failing the whole run with "There are suppressions left that do not occur anymore", turning `npm run lint` red on `release/v3.8.49` for every PR branched off it
- chore(release): merge-train runs the box-tuned `test:unit` (concurrency 20) instead of two sequential 4-core CI shards (~5× faster suite) and gains an owner-approved `--fast` mode (static gates + changed tests + vitest) for intra-day mega-train drains
- **chore(release):** merge-train `--fast` now classifies changed tests with the same
`tests/unit/autoCombo/`) are skipped instead of misrun under node:test, and
`tests/unit/ui/**/*.test.ts` (node:test) is no longer over-excluded.
- **CI**: quality.yml draft guards now also match Mergify speculative merge-queue PRs (`mergify/merge-queue/*` heads are drafts by design) — without this every queued batch failed its anchor check in 2s and dequeued
- **Merge queue (D5)**: reviewed PRs now merge through the Mergify queue (`.mergify.yml`, Open Source plan) — entry is the owner-applied `queue` label AFTER the pre-merge ⭐ gate; batches of up to 10 validate together with automatic bisection of red batches (~log2(N) instead of N revalidations); the manual merge-train is codified as the fallback runbook in `docs/ops/MERGE_TRAIN.md` with the freeze/cross-session guardrails
- **Release**: npm publishing is now STAGED by default — the workflow boots the packed tarball (`check:pack-boot`) and runs `npm stage publish` (bytes parked on the registry, not installable); the owner verifies the staged bytes and releases them with `npm stage approve` + 2FA, moving the human gate to after the proof (the structural fix for the #7065 broken-tarball class); `publish_mode=direct` remains as a documented emergency fallback
- chore(release): v3.8.47 pre-flight — relocate #6943 orphan test to a collected path, restore no-explicit-any suppression match, testFrozen bump translator-openai-to-gemini (1541→1553), zizmor rebaseline 159→169
- **Docs**: `QUALITY_GATES.md` now codifies the per-runner test retry policy (Playwright 1 CI retry with trace; Vitest per-test explicit quarantine only; node:test never) with target flake SLOs, and the release-level ratchet-drift rule (combination drift on the pure tip is the release captain's to fix once on the branch — never pushed onto contributor PRs, never rebaselined per-PR)
- chore(quality): owner-approved ratchet rebaseline for the v3.8.49 merge campaign — complexity 2072→2130, cognitive-complexity 900→950 (the day's 17 merged PRs consumed the entire slack; wide margin chosen over per-PR extraction for the rest of the cycle)
- **docs(readme):** fix stale counts — 18 routing strategies (adds the missing `pipeline` row), 94 MCP tools, 12-factor Auto-Combo scoring.
- Rebaseline the cyclomatic-complexity ceiling (2059 → 2072) and cognitive-complexity ceiling (890 → 900) to absorb a cluster of clean contributor feature PRs whose per-PR +1/+2 own-growth collectively exceeded the tip's 3-unit slack. Owner-approved; structural shrink remains tracked in #3501.
- **CI**: release-green validation is now continuous — every code push to `release/v*` (including the captain's direct sync-back pushes, the one previously ungated write path) triggers a `--quick` HARD-gate run with per-branch superseded-run cancellation, and the tracking issue names the offending push range; the deep `--with-build --full-ci` sweep now runs 3×/day instead of nightly-only (base-red MTTD: ≤24h → ~15min after the offending push)
- **chore(ci):** fix two shared base-reds on the release tip that blocked the PR queue — register `cliproxyapi-model-mapping-dispatch.test.ts` in `stryker.conf.json``tap.testFiles` (mutation-coverage gap left by #6903) and update `provider-models-route-codex.test.ts` to expect Codex client version `0.144.0` (stale assertion left by #6780's production bump).
- **docs:** refresh `llm.txt` to the current project state (248 providers, 94 MCP tools / 30 scopes, 18 routing strategies, 12-factor Auto-Combo scoring, v3.8.47) and sync its 42 i18n mirrors; move the implemented design-system plan from the repo root to `docs/architecture/DESIGN_SYSTEM.md` rewritten as a reference doc.
- Strengthen the `compareRouterEvalRuns` regression test to assert the actual AIQ and cost regression reasons instead of counting entries, clearing the weakened-assert (test-masking) gate on the release tip.
- **Ops**: `scripts/ops/runner-janitor.sh` + `docs/ops/RUNNER_BOX.md` codify the self-hosted runner box hygiene that was manual discipline — 30min cron sweeping stale runner temp dirs, alerting at ≥85% disk, and enforcing the proven 4-runner ceiling on the 16 GB box (8-wide OOM-killed jobs twice on the v3.8.47 release day)
- **chore(security):** scrub hardcoded live-instance credentials (API key + auth cookie + host URL) from the `tests/boundary/*.live.test.ts` files landed via #6786 — they now read `OMNIROUTE_TEST_BASE` / `OMNIROUTE_TEST_BEARER` / `OMNIROUTE_TEST_COOKIE` from the environment and stay gated behind `RUN_BOUNDARY_LIVE=1`.
- **Tests**: the #6634 self-reference test now fetches `origin/main` on demand and skips cleanly when the ref is unreachable — it failed as a false positive on shallow/single-ref checkouts (GitHub-hosted runners)
- Register 5 covering unit tests (account-fallback lockout eviction, cliproxyapi dedicated credential #7645, combo least-used account, combo recovery-hint, route-guard forge/jcode local-only) in `stryker.conf.json``tap.testFiles` to clear the release base-red.
- chore(quality): register microsoft-designer-web-6672 test in stryker tap.testFiles (unblocks Fast Quality Gates base-red)
- **Release tooling**: `sync-next-cycle.mjs` now runs `validate-release-green --quick` on the merged tree BEFORE pushing the parallel-cycle sync-back — the one write path to the release branch that had no CI gate; a red merged tree stays local instead of turning the whole PR queue red (`--skip-green-gate` is the documented emergency hatch)
- Rebaseline `testFrozen` for `tests/unit/providers-page-utils.test.ts` (1107 → 1294) after #7775 legitimately grew it with the Kimi provider pinning + supporter-card coverage; the merged PR did not update the baseline, leaving the `test-file-size` gate red on the release tip.
- **chore(quality):** tighten the coverage ratchet to the CI's real numbers (branches 73→78.1, statements/lines 76.5→80.8, functions 82→86.44, plus 7 per-module floors). The gate had been asking for this in plain text; the values come from the merged-coverage run on `main`, not a local run (local measures ~68% vs CI's ~80% — the baseline's own note warns about that gap).
- **chore(quality):** split `open-sse/services/tokenRefresh.ts` (2249 lines, frozen) into per-provider leaves — `open-sse/services/tokenRefresh/providers/{windsurf,codebuddyCn,cline,kimiCoding,gitlabDuo,claudeOAuth,google,codex,kiro,qoder,github,copilot}.ts` plus a `tokenRefresh/shared.ts` for the cross-provider OAuth-error classifier and form-body helper — pure move, zero behavior change; the file shrinks to ~989 lines and every previously-public export (`refreshCopilotToken`, `getAccessToken`, `extractOAuthErrorCode`, etc.) is re-exported unchanged so no importer needed to change. Idea originally proposed by @KooshaPari in #7338 against a much older base; redone here against the current tip.
- chore(quality): re-pin the file-size ceilings touched by the v3.8.49 merge-train 1H (31 PRs) and register three covering tests that had drifted out of `stryker.conf.json` — the mutation gate was failing `Fast Quality Gates` on every new PR
- chore(quality): re-pin the `chatCore.ts` file-size ceiling for the Responses multi-turn image compaction added in #8595
- chore(quality): align the pack-artifact expected-path fixture with the Android/Termux cache module added in #8593, and re-pin the `chatCore.ts` ceiling grown by the merge-train 2 batch
- chore(quality): re-pin the `apiKeys.ts` and `chatCore.ts` ceilings grown by the merge-train 3 batch, and register the Cloudflare-1010 regression test in `stryker.conf.json`
- **CI**: Playwright E2E and both vitest suites now emit JUnit and upload to Trunk Flaky Tests (org `omniroute`) — advisory step, own-origin only, uploader action SHA-pinned (WS5.2/5.3 of the quality plan; node:test stays out of the first wave)
- **CI**: the fast-path Vitest job (every PR) now also emits JUnit and uploads to Trunk Flaky Tests — the heavy-gate uploads alone (release PR only) would never accumulate flaky-detection volume
- **CI**: TypeScript 7 (native compiler, GA 2026-07-08) now runs as an advisory SHADOW of the blocking `typecheck:core` gate on the fast path — same tsconfig, isolated `npx` (no dependency change; the Compiler API only lands in TS 7.1, so typescript-eslint/type-coverage/Stryker stay on 6.x). Local parity proven (0 errors on both, exit 0); promotion to the blocking gate after ~1 week of CI parity
- **maintenance(quality):** clear v3.8.49 owner-PR-campaign base-reds on `release/v3.8.49` (accumulated because fast-gates PR→release skip the complexity/golden/file-size ratchets, surfaced only on re-sync): regenerate the `provider/translate-path` golden snapshot for the new providers; re-point the `#6772` connId-prefix test off the now-reserved `fta` alias (claimed by freetheai #7602 — built-in aliases shadow custom-node prefixes by design); bump the complexity baseline 2056→2058 for the new provider dispatch handlers. No behavior change.
- **Release tooling**: new `scripts/release/verify-published.mjs <version>` — post-publish net that installs the published version from the public registry inside a clean `node:24-slim` container and boots it until `/api/monitoring/health` reports the expected version (validates the exact bytes users install, on a machine with no repo/devbox state); wired into the release Phase 4 monitoring playbook
- **CI**: promote `test:vitest:ui` to a blocking gate — the suite is 870/870 green again after the WS6.1 triage (#7127), so `continue-on-error` is removed from the vitest job
- chore(tests): fix all 70 failing `test:vitest:ui` tests across 30 files (was advisory/parked) — root causes were 15 node:test-authored `.tsx` files never collected by vitest, a missing `window.matchMedia` jsdom polyfill, stale assertions against a redesigned BuildTab wizard / CompressionHub Phase-2 UI, and one obsolete test for a retired Plans screen; suite is now 158/158 files, 870/870 tests green (promotion to blocking is a follow-up)
### 🙌 Contributors
Thanks to everyone whose work landed in v3.8.49:
@@ -628,87 +1226,158 @@ Thanks to everyone whose work landed in v3.8.49:
- **feat(sse):** every completion response now carries an `X-OmniRoute-Decision: strategy=<name>; provider=<alias>; latency_ms=<n>` header exposing the routing decision — `<name>` is the combo strategy (`priority`, `weighted`, `fusion`, etc.) or `single` for a non-combo request — for client-side debugging/analytics without server log access (#6022 — thanks @chirag127).
- **feat(ws):** the live-dashboard WebSocket server now auto-starts in-process (via `instrumentation-node.ts`) across every deployment mode — dev, production, Docker, Electron — with no separate sidecar script; the default WS port moved from 20129 to **20132** to avoid colliding with `API_PORT` in split-port setups, the deprecated `OMNIROUTE_DISABLE_LIVE_WS` env was consolidated into `OMNIROUTE_ENABLE_LIVE_WS` (default enabled), and the WS path is now derived from `NEXT_PUBLIC_LIVE_WS_PUBLIC_URL`'s pathname (`/live-ws` fallback) (#6072 — thanks @ianriizky).
- **feat(dashboard):** Replace free-text model inputs in the Routing (web search route), Combo Defaults (handoff model), and Background Degradation tabs with a `hidePaidModels`-aware `ModelSelectField`, add a fail-open "paid-only pattern" warning to the per-model routing rule pattern field, and reject paid-only model targets at save time on `PATCH /api/settings`, `PATCH /api/settings/combo-defaults`, and `PUT /api/settings/background-degradation` when `hidePaidModels` is on ([#6540](https://github.com/diegosouzapw/OmniRoute/issues/6540))
- **feat(compression):** new **omniglyph** engine (context-as-image) — renders system prompt, tool docs, and dense history as compact PNG pages the model reads instead of text (~10× fewer tokens on the converted block; 59–70% end-to-end measured). Works stacked with RTK/Caveman (`stackPriority: 90`) or standalone (`mode: omniglyph`); restricted to Claude Fable 5 over the direct Anthropic route, fail-closed gates with `skip:<reason>` techniques, preview (`stable: false`, off by default) (#6556). Dependency bumped to `omniglyph@^1.0.2` for upstream ReDoS fixes (#6661).
- **feat(sse):** rate-limit request queue admission control — `resilienceSettings.requestQueue.maxQueueDepth` (default `0` = disabled, opt-in 0–100000) fast-rejects a request with a typed `RATE_LIMIT_QUEUE_FULL` error once the local per-provider+connection queue already holds `maxQueueDepth` requests, instead of growing the queue unboundedly; the factory default for `requestQueue.maxWaitMs` (how long a request may wait before being dropped) also fell from 120s to 15s so a saturated queue fails fast (#6593 — thanks @chirag127).
- **feat(sandbox):** the skill sandbox gained a container-provider abstraction that auto-detects and uses the best native runtime per host — Apple Container (macOS 26+), WSL container (`wslc.exe`), OrbStack, Podman — instead of hardcoding `docker run`, removing the Docker Desktop requirement on macOS/Windows (#6611 — thanks @KooshaPari).
- feat(oauth): accept the full ChatGPT session JSON (not just a bare access token) when pasting Codex credentials manually or via `POST /api/oauth/codex/import-token` (#6636)
- **feat(providers):** add **Segmind** as an image + video generation provider — `x-api-key` auth against `POST https://api.segmind.com/v1/{model}`, with a curated starter model list (Flux, Stable Diffusion XL/3.5, Kandinsky for image; Wan, Hunyuan, LTX, Kling for video) (#6656).
- **feat(providers):** add Speechmatics as an STT provider — async batch transcription (Enhanced operating point), 8 hours/month free tier, no credit card required. Streaming (real-time) mode is out of scope for v1. (#6659)
- **feat(providers):** add Felo (felo.ai) as a free, no-signup, no-API-key chat/search-agent aggregator provider (`felo-web`) — joins the existing `-web` family (DuckDuckGo AI Chat, Blackbox, etc). Five models (`felo-chat`, `felo-search`, `felo-scholar`, `felo-social`, `felo-document`) map to Felo's search categories; the executor opens a search thread then translates Felo's bespoke SSE stream into OpenAI-compatible chunks (#6666).
- **feat(providers):** add gTTS (Google Translate TTS) as a free, no-signup audio-speech provider — routes through Google's current `batchexecute` RPC endpoint (the previously proposed `translate_tts` endpoint is deprecated), splitting input at the 100-char-per-request limit. (#6667)
- **feat(sse):** add EdgeTTS (Microsoft Edge "Read Aloud") as a free, no-API-key `audio-tts` provider — the first WebSocket-transport speech provider, with per-client-IP rate limiting. (#6668)
- **feat(providers):** add Hailuo Web (`hailuo-web`) — a free, `_token`-based web-cookie chat provider for the MiniMax consumer chat product at hailuo.ai, ported from the g4f reference implementation (MD5-chain request signing, custom `event:`/`data:` SSE parsing). Distinct from the existing paid API-key `minimax`/`minimax-cn` providers. (#6673)
- **feat(cli):** new `omniroute auth export` command dumps DECRYPTED provider credentials (`apiKey`/`accessToken`/`refreshToken`/`idToken`) for one connection (`--id <id>`) or all connections, as `json` or `env` (`--format`), local-only and gated behind `--force` — no DB access happens without it, a stderr warning banner prints before any plaintext, `--out <file>` writes with `0600` permissions, and per-field decrypt failures surface as a `<field>DecryptFailed` boolean instead of aborting the export or leaking the caught error text (#6683)
- **feat(mitm):** the AgentBridge static MITM server (`server.cjs`) can now issue a per-host TLS leaf from a persisted local root CA (`src/mitm/cert/rootCa.ts`, reusing the CA/leaf crypto already proven for TPROXY in `tproxy/dynamicCert.ts`) instead of a single static self-signed leaf scoped only to the 4 antigravity hosts — a fresh install covers the full `MITM_TOOL_HOSTS` set automatically; an install that already trusted the old leaf keeps using it until the operator opts in via `MITM_ROOT_CA_ENABLED=true` (`src/mitm/cert/migration.ts`), so no existing install is silently upgraded to the more powerful any-host-signing CA trust model (#6684).
- **fix(sse):** skip `thinkingConfig` for Gemma models on the OpenAI→Gemini path so OpenAI-shape clients no longer get a 400 from Vertex. (thanks @chy1211)
- **feat(models):** add a Settings → AI "Model Overrides" UI plus `/api/model-capability-overrides` CRUD and a `model_capability_overrides` table, letting operators set a manual max-output-token override per provider/model (#6727 — thanks @xz-dev).
- **feat(sse):** preserve `tools`/`tool_choice` for tool-bearing requests through fusion combos — bypass panel synthesis and route straight to the judge with tools intact (#6771 — thanks @chirag127).
- **feat(codex):** Codex provider model discovery now fetches the live catalog from `chatgpt.com/backend-api/codex/models` using Codex-shaped headers, falling back to a GitHub-hosted model manifest and then to the local static catalog when the live/GitHub sources are unavailable or return an unexpected shape — new `src/app/api/providers/[id]/models/discovery/codex.ts` (normalization, version-gating, merge/enrich against the local catalog) covered by `tests/unit/provider-models-discovery-split.test.ts` and `tests/unit/provider-models-route-codex.test.ts` (#6776 — thanks @JxnLexn).
- **feat(cursor):** register the Opus 4.8, Fable 5, and Sonnet 5 model families for the Cursor Agent provider so the latest Claude/Fable model ids route correctly (#6779 — thanks @andrewmunsell).
- **Changelog fragments (`changelog.d/`)**: PRs now add their changelog entry as a new fragment file (`changelog.d/{features|fixes|maintenance}/<PR>-<slug>.md`) instead of editing `CHANGELOG.md` — two PRs never touch the same file, structurally eliminating the CHANGELOG-eat merge conflicts that forced a re-sync push + full CI re-run after every sibling merge (O(N²) CI runs in a merge-storm). `scripts/release/aggregate-changelog.mjs` (npm run changelog:aggregate) folds fragments into the living section at release reconciliation, and `check:changelog-integrity` now also validates fragment well-formedness. Regression guard: `tests/unit/changelog-fragments.test.ts`.
- **feat(fusion):** the fusion judge may now draw on its own knowledge and override the panel when every panel answer is wrong or incomplete, instead of being restricted to synthesizing only from panel output (#6804 — thanks @chirag127).
- **feat(dashboard):** search box on the Playground's raw model `<select>` (#4086) — the shared `ModelSelectModal` (combo builder + CLI-code cards) already had search, but Playground's `StudioConfigPane` model dropdown stayed a flat unsearchable list, unusable once a provider like OpenRouter contributed 50+ models. Typing now filters the dropdown (Turkish-safe accent/case-insensitive match via `matchesSearch`), while the currently selected model always stays pinned in the list even if it no longer matches the query, so typing never silently swaps the active selection. Reuses the existing `common.search` i18n key (already translated in all 42 locales) — no new translation key needed. Regression guard: `tests/unit/playground-model-selection-3731.test.ts` (`filterModelsByQuery`), `tests/unit/ui/playground-model-search-4086.test.tsx`.
- **feat(usage):** Antigravity/agy quota widget now surfaces the **weekly** window alongside the existing per-model 5-hour window ([#4017](https://github.com/diegosouzapw/OmniRoute/issues/4017)) — the weekly limit isn't part of the per-model `retrieveUserQuota` response the fetcher already calls; it only appears in a separate, undocumented `retrieveUserQuotaSummary` RPC that groups models into families ("Gemini Models", "Claude and GPT models") with one weekly bucket per family. A new `usage/antigravityWeeklyQuota.ts` leaf fetches that RPC (cached, best-effort — a failure or unavailable RPC never breaks the existing per-model quotas) and parses the weekly bucket per group into `gemini_weekly`/`claude_gpt_weekly` quota entries, merged into the same `quotas` map the widget already renders generically. Regression guard: `tests/unit/antigravity-weekly-quota-4017.test.ts` (bucket parsing, the alternate `quotaSummary`-nested envelope, and end-to-end merge via `getUsageForProvider`).
- **feat(codex):** Codex CLI compatibility shim — the Responses API `response.created`/`response.in_progress`/`response.completed` payloads now carry a `model` field (previously absent), and for Codex-CLI-originated requests it echoes the client-requested, effort-suffixed model id (e.g. `gpt-5.5-xhigh`) instead of the bare upstream id (`gpt-5.5`), so the Codex CLI status line/model button shows the active reasoning effort ([#3697](https://github.com/diegosouzapw/OmniRoute/issues/3697)). `openaiToOpenAIResponsesResponse` (`open-sse/translator/response/openai-responses.ts`) now threads the upstream model into the Responses event objects; a new `isCodexOriginatedHeaders()` (`open-sse/config/codexIdentity.ts`, reusing PR #3481's `originator`/User-Agent detection) makes chatCore's existing opt-in `echoRequestedModelName` (#1311) model-echo pipeline fire automatically for Codex clients regardless of the setting, detected by request headers so it still applies when `codex/gpt-5.5-xhigh` is routed through a combo to a non-codex upstream; `echoModelInObject`/`echoModelInSseLine` (`open-sse/services/responseModelEcho.ts`) now also rewrite the nested `response.model` field the Responses API uses. `/v1/models` still returns `models: []` for Codex (unchanged). Regression guard: `tests/unit/codex-effort-model-echo-3697.test.ts`.
- **Z.ai Web (free web-session provider)**: new `zai-web` web-cookie provider drives the free chat.z.ai consumer chat UI via a pasted browser session cookie, distinct from the existing API-key `zai`/`glm`/`glm-cn`/`glmt` providers (`api.z.ai`) — modeled on the `doubao-web`/`venice-web` cookie executors and the pre-existing `chatglm-web` credential requirement/token-extraction entries. `ZaiWebExecutor` (`open-sse/executors/zai-web.ts`) posts to `chat.z.ai/api/chat/completions` with the cookie forwarded both as `Cookie` and as `Authorization: Bearer <token>`, and normalizes both z.ai's internal `delta_content`/`phase` SSE envelope and a pass-through OpenAI-shaped `choices[].delta` frame into standard chat-completion chunks. Registered in `WEB_COOKIE_PROVIDERS`, `WEB_SESSION_CREDENTIAL_REQUIREMENTS`, the provider registry (`zai-web` entry, GLM-4.6/4.5/4.5V models), and `tokenExtractionConfig.ts` for in-app cookie capture. Regression guard: `tests/unit/executor-zai-web.test.ts` (16 tests — token extraction, frame parsing for both SSE shapes, streaming and non-streaming aggregation, error paths). (#4056)
- feat(dashboard): import multiple, possibly different providers from a CSV/JSON file — per-row validation, a checklist to pick which parsed rows to import, and a new `POST /api/providers/import` route with partial-failure results (#6836)
- **feat(compression):** update the vendored **GCF** codec behind the **Headroom** engine to spec **v3.2 (nested flattening)** ([#6837](https://github.com/diegosouzapw/OmniRoute/issues/6837)). Homogeneous arrays whose rows carry nested objects/arrays now tabularize via `>`-prefixed path fields instead of a low-yield per-row fallback, so nested MCP tool-result rows (`meta:{...}`, `tags:[...]`) compact like flat rows. On representative shapes the update takes deeply-nested payloads the old codec left near-uncompressed from ~3% to ~32% vs JSON (k8s pods, `cl100k_base`), with shallow-nested rows seeing a small bump and flat arrays unchanged. Re-vendored from current gcf-typescript (zero runtime deps, MIT, SPDX-marked, generic-profile only); also folds in the `[N]:` inline-array quoting fix and canonical decimal formatting. Round-trip stays lossless (order-insensitive), and the decoder is hardened against prototype pollution (a `__proto__`/`constructor` path segment never mutates `Object.prototype`, and keys shadowing built-ins like `toString` now round-trip correctly instead of misparsing). Regression guard: `tests/unit/compression/headroom-smartcrusher.test.ts` (deep-nested + prototype-pollution cases).
- **feat(sse):** OpenRouter quota tracking — a dedicated fetcher polls `/api/v1/key` + `/api/v1/credits` (per-key credit cap/remaining/reset, daily/weekly/monthly USD spend, BYOK usage) with a 45s cache and graceful degradation, a local per-account counter tracks the `:free`-model 50-or-1000-per-day + 20 RPM windows (corrected from `X-RateLimit-*` headers and `Retry-After` on 429), and OpenRouter `402` responses now lock the connection with a real cooldown instead of triggering an immediate reselection of the same credit-exhausted key (#6842).
- **feat(sse):** live gRPC-web quota fetcher for Grok Build (`grok-cli`) — polls xAI's shared weekly credit pool (`grok.com/grok_api_v2.GrokBuildBilling/GetGrokCreditsConfig`) with the connection's existing bearer token, hand-decoding the framed/raw protobuf response (proto3 omission = 0% used, malformed/expired-token fails open) instead of relying solely on the static 864 req/day estimate, which now serves as an explicit fallback when the live fetch is unavailable (#6844).
- feat(sse): add dual-window quota tracking for the `v0-vercel` provider — polls the credits (`/v1/user/billing`) and daily Platform-API operation (`/v1/rate-limits`) endpoints with the existing routing API key, defensively degrading to an `unknown` billing type rather than misparsing a future v0 billing-model migration, feeding preflight + the dashboard's Provider Quota card (#6845).
- feat(sse): add a static local RPM budget (default 40/min, operator-overridable), per-model 429 scoping (already covered by #6773's `passthroughModels`), and a per-connection concurrency cap (default 6, operator-overridable) for the `nvidia` (NVIDIA NIM) provider, which sends no rate-limit headers and has no usage API — Phase 1 of client-side quota tracking; adaptive AIMD learning and the dashboard quota card are deferred follow-ups (#6846).
- feat(sse): add quota tracking for the `agentrouter` provider — polls the New-API `/api/user/self` balance endpoint with a separate System Access Token + `New-Api-User` id (configured via `providerSpecificData.consoleApiKey` / `newApiUserId`), converting raw quota units into a dollar balance and feeding preflight + the dashboard's Provider Quota card (#6850).
- **feat(providers):** Add GPT-5.6 support across OpenAI API, Codex, and ChatGPT Web, including Codex Max/Ultra efforts, VS Code metadata, Fast-tier credit accounting, curated live discovery, the Codex 0.144.1 client identity, and correct chat routing for models that also support image generation ([#6862](https://github.com/diegosouzapw/OmniRoute/pull/6862)) - thanks @backryun
- **chore(providers):** Align emitted Claude Code identity headers, bridge fingerprints, provider profiles, and documented defaults with claude-cli 2.1.207 ([#6862](https://github.com/diegosouzapw/OmniRoute/pull/6862)) - thanks @backryun
- **feat(providers):** add a `vibeproxy-openai` provider-node preset to `POST /api/provider-nodes` — defaults name/prefix/apiType for VibeProxy's local OpenAI-compatible gateway and normalizes the caller-supplied base URL to its `/v1` root; `baseUrl` remains mandatory. (#6874, idea from #6137 by @KooshaPari)
- feat(usage): add avgTtftMs/avgE2ELatencyMs/avgTokensPerSecond to `getModelLatencyStats()` and feed them into auto-combo's speed-ranking factor (#6875)
- feat(sse): per-model default `reasoning_effort` (`ModelSpec.defaultReasoningEffort`, injected only when the request carries no reasoning field) and make `no-think/` express `reasoning_effort:"none"` instead of deleting the field on the OpenAI path, so thinks-by-default models actually stop thinking (#6879)
- **feat(providers):** let a custom/openai-compatible connection opt into prompt-cache behavior via a per-connection `cache` capability override, unblocking `prompt_cache_key` injection, the compression cache-aware guard, and `cache_control` passthrough for `openai-compatible-chat-<uuid>`-style connections. (thanks @andrea-kingautomation)
- **feat(dashboard):** add a Type filter (No Signup / OAuth Login / API Key) and an "Easiest first" sort toggle to Free Provider Rankings, so zero-setup NOAUTH providers can be surfaced without eyeballing the Type column. (#6915)
- **feat(providers):** expose an editable base-URL field on the ComfyUI connection so Docker-network setups (e.g. `http://comfyui:8188`) work for image, video, and music generation ([#6928](https://github.com/diegosouzapw/OmniRoute/issues/6928))
- **feat(providers):** refresh the curated OpenRouter embeddings catalog (`open-sse/config/embeddingRegistry.ts`) with the current lineup — `openai/text-embedding-3-small`/`-large`, `qwen/qwen3-embedding-8b`/`-4b`, `baai/bge-m3`, `mistralai/mistral-embed-2312`, `google/gemini-embedding-001` — and fold curated embedding/rerank entries into OpenRouter's live model-discovery response (`src/app/api/providers/[id]/models/route.ts`), additively and deduped by id, so they no longer only appear on the no-config `local_catalog` fallback. OpenRouter serves embeddings via a dedicated `/api/v1/embeddings` endpoint (omitted from `/v1/models`), so the live-discovery success path previously returned chat models only ([#6976](https://github.com/diegosouzapw/OmniRoute/issues/6976)). Regression guard: `tests/unit/openrouter-embeddings-catalog-6976.test.ts`.
- **feat(quota):** Add opt-in auto-ping to keep Codex quota windows warm — per-connection toggle in Settings → AI that sends a tiny request right after a Codex session window resets, so it isn't cold on the next real request ([#6977](https://github.com/diegosouzapw/OmniRoute/issues/6977))
- **feat(oauth):** Add a one-click browser (PKCE) login for Grok Build (`grok-cli`) ALONGSIDE the existing device-code flow — reusing the same `auth.x.ai` authorize/token endpoints and public client id as the sibling `xai-oauth` provider on its own loopback port — while keeping the pre-existing device-code method and the paste-token/`auth.json` import flow both available; the connect modal lets the user pick "Device Code", "Browser Login", or "JWT Token" ([#7013](https://github.com/diegosouzapw/OmniRoute/issues/7013))
- **feat(auth):** accept the `x-goog-api-key` header for client-facing auth so `gemini-cli` and other `@google/genai`-based clients can use OmniRoute as a native `/v1beta` gateway (#7034 — thanks @QRcode1337).
- feat(sse): add a local dual-window (5h + 7d, per-account) quota tracker for the `freemodel-dev` provider, feeding preflight + the dashboard's Provider Quota card with user-overridable request caps (`FREEMODEL_5H_REQUEST_LIMIT` / `FREEMODEL_7D_REQUEST_LIMIT`) since FreeModel publishes no usage API — phase 1 of #7075 (tracker + registration); live hot-path request metering and the forward-compatible endpoint prober are tracked as follow-ups.
- **feat(api):** opt-in scheduled auto-sync for free-proxy sources (`src/lib/freeProxyProviders/scheduler.ts`) — periodically re-runs the free-proxy provider `sync()` calls (iplocate, proxifly, oneproxy, webshare) that previously only ran via a manual `POST /api/settings/free-proxies/sync`, so a seeded pool no longer goes stale as free-proxy lists rotate; gated by `FREE_PROXY_AUTO_SYNC_ENABLED` (default `false`) and `FREE_PROXY_AUTO_SYNC_INTERVAL_MS` (default 30min, floor-clamped to 5min), reuses the existing `isBuildProcess()`/`OMNIROUTE_DISABLE_BACKGROUND_SERVICES` guards, and shares one `runFreeProxySyncCycle()` code path with the manual route; `GET /api/settings/free-proxies/stats` now also reports `autoSync: { enabled, intervalMs }` (#7079 — thanks @chirag127).
- **feat(dashboard):** show the Codex subscription plan label in provider connection rows and the quota view, falling back to the plan captured at OAuth import when the live usage endpoint doesn't report one. (thanks @CarmeloCampos)
- **feat(dashboard):** add a "Reorder" button to provider connections that sorts them by availability (using OmniRoute's connection-cooldown/testStatus model), persisting the new priority order. (thanks @fzrilsh)
- **feat(dashboard):** add 180D and 365D periods to the Cost Explorer range selector. The new ranges thread through `parseCostRange`/`COST_RANGE_VALUES` and the `getRangeStartIso` handlers in the analytics and requests-by-provider-date usage routes, so cost/usage analytics can be viewed over a half-year and full-year window (#7213)
- **feat(sse):** GitHub Copilot Claude models now route through Copilot's native `/v1/messages` endpoint (prompt-cache token counts, no more lossy tool-call round-trip). (thanks @yidecode)
- **feat(mitm):** Antigravity MITM model mappings now support an optional per-model reasoning-effort override (Default/None/Low/Medium/High/XHigh) alongside the destination-model remap. (thanks @trfi)
- **feat(sse):** add native xAI Grok Imagine video generation provider — `xai/grok-imagine-video` on `/v1/videos/generations` using your own xAI key, instead of only via the kie proxy market. (thanks @anndev-69)
- **feat(cli):** add Grok Build CLI tool setup — writes a `[model.omniroute]` custom model into `~/.grok/config.toml` and restores your previous default on Reset. (thanks @rixzkiye)
- **feat(dashboard):** add a per-operator "hide this quota row" toggle to the usage dashboard's provider limit cards, persisted per-provider in settings. (thanks @nguyenha935)
- **feat(sse):** session affinity (`X-Session-Id` / `x-codex-session-id` / `x-omniroute-session`) now works for **any** provider, not just Codex — the `codex`-only early-return in `resolveSessionAffinityTtlMs()` was removed, and the global TTL setting was renamed `codexSessionAffinityTtlMs` → `sessionAffinityTtlMs` (dashboard label updated to "Session affinity") with a backward-compatible migration that carries over an existing Codex TTL as the new default (#7274 — thanks @tenshiak).
- **feat(sse):** `gemini-web` now honors the OpenAI `tools` array by wiring the existing `webTools.ts` prompt-emulation shim (already used by 11 other web-cookie executors) — the tool contract is serialized into the prompt typed into the Gemini web UI, and `<tool>{...}</tool>` blocks in the response are parsed back into `tool_calls` (malformed JSON degrades to ordinary chat content, never an error), including for streaming requests; `docs/reference/PROVIDER_REFERENCE.md` gained a "Tool calling" column (`native`/`emulated`/`none`) for providers with confirmed ground truth. `claude-web` still silently drops `tools` pending a separate emulate-vs-native design decision (#7286).
- **feat(eval):** added a router-eval harness (`npm run eval:router`, `eval:router:compare`, `eval:router:patch-compare`, `eval:router:search`, `eval:router:trends`, `check:router-eval`) that replays routing decisions — from NDJSON corpora or the `usage_history`/`call_logs` SQLite tables — into an AIQ (success/latency/cost) score, compares baseline vs. candidate router configs with a retained-run regression gate, and ranks Pareto-optimal candidates; a sibling tool to the existing `eval:compression` harness (#7318 — thanks @KooshaPari).
- **feat(services):** introduce a `ServiceProviderPlugin` contract (`src/lib/services/providerPlugins/`) that packages what was previously spread across `bootstrap.ts`'s inline `SERVICES[]` config and `serviceBackends.ts`'s manifest template into one shape per embedded-service backend; migrates `9router` through it end to end as the first proof, with `cliproxy`/`mux`/`bifrost` left completely unchanged on their pre-existing code paths — `cliproxyapi` migration, executor-routing consolidation, and manifest-path wiring are follow-up work (#7333).
- feat(routing): wire `interceptFetch` into the chat pipeline — `resolveInterceptFetch(provider, model)` (a structural twin of `resolveInterceptSearch`) and the new `open-sse/services/webFetchInterception.ts` module rewrite a provider-native `web_fetch` tool declaration into a synthetic `omniroute_web_fetch` function tool, dispatched through the existing tool-call execution path to OmniRoute's own `/v1/web/fetch` instead of the upstream provider running it natively; strictly opt-in per provider/model (undefined/off leaves the request byte-identical to today), with a new dashboard toggle on the provider detail page covering both `interceptSearch` and `interceptFetch` (#7339, Phases 3-4 of #3384)
- **feat(dashboard):** surface in-product guidance for the Settings → Prompt Compression engines — each `engineCatalog.ts` entry now carries a `guidance` block (quality/latency tradeoffs, lossy flag, cache impact) sourced from `docs/compression/*.md`, rendered as an expandable per-engine detail with a "safe default" indicator for lossless engines (Session Dedup, CCR, Lite, Headroom) plus a link to the full compression guide (#7530).
- **feat(providers):** Complete GHE Copilot OAuth provider implementation with device-code flow, validation, error sanitization, and DNS provisioning mapping ([#7546](https://github.com/diegosouzapw/OmniRoute/pull/7546))
- feat(providers): grok-web's add-connection dialog now shows a proactive hint explaining that grok.com's `cf_clearance` cookie is pinned to the IP, User-Agent, and TLS fingerprint of the browser that earned it — on a 403, paste the exact browser User-Agent into the existing Custom User-Agent field under Advanced Settings and reuse the same IP/proxy (#7567)
- **feat(providers):** register `lmstudio` in the embedding provider registry (`open-sse/config/embeddingRegistry.ts`) — LM Studio's local OpenAI-compatible `/v1/embeddings` endpoint, no auth required, passthrough model list (a user-configured provider_node still takes priority). Previously any `lmstudio/<model>` embedding request failed with `"Unknown embedding provider: lmstudio"` even though the model appeared fine in `/v1/models` ([#7601](https://github.com/diegosouzapw/OmniRoute/issues/7601) — thanks @ekinnee). Regression guard: `tests/unit/lmstudio-embedding-provider-7601.test.ts`.
- feat(sse): honor a no-auth provider connection's **Excluded Models** field (`providerSpecificData.excludedModels`) in the auto-combo/fusion candidate pool builder, so an excluded no-auth model (e.g. `minimax-m3-free`) is filtered out upfront instead of only failing over after being picked (#7622).
- **feat(dashboard):** proxy-assignment UX quality-of-life fixes — the per-connection proxy badge now shows the saved proxy's **name** instead of always falling back to its hostname (so distinct proxies sharing a domain no longer render identically), the "Proxy Config" modal's saved-proxy dropdown is now alphabetized by name, and the modal defaults to the **Saved** tab (instead of Custom) whenever the target scope has no assignment yet and at least one saved proxy already exists (#7643 — thanks @tenshiak).
- feat(providers): `zai-web` (chat.z.ai) now wires live `/models` discovery through the shared PROVIDER_MODELS_CONFIG pipeline (cache → hardcoded local-catalog fallback), instead of only serving a static 3-model catalog whose ids had started 404ing upstream (#7678 — thanks @andrea-kingautomation). The `chat.z.ai/api/models` response shape and Bearer-only auth are unverified pending a live cookie check before merge.
- **feat(perplexity):** Refresh Perplexity Web model mappings and Search API filters while keeping `/v1/models` scoped to API-key validation instead of curated catalog discovery ([#7687](https://github.com/diegosouzapw/OmniRoute/pull/7687)) — thanks @backryun
- Generic OpenAI-compatible model sync now captures a provider's upstream `reasoning.supported_efforts` (in addition to the pre-existing flat `supportedThinkingEfforts` shape), surfaces it in the model catalog as `capabilities.effort_tiers`, and advertises `<prefix>/<model>-{effort}` request-time alias ids for catalog-only clients — mirroring the existing per-model reasoning-effort plumbing (#6879). Codex and Kimi are excluded since they already own their own effort-suffix mechanism.
- **feat(dashboard):** Kimi (Moonshot AI) official-partnership highlight on the providers dashboard — Kimi-family providers (`kimi-coding`, `kimi-web`, `moonshot`) now render first within their category/group, and their card gets a Kimi-blue (`#1783FF`) accent border/glow plus an "Official Supporter" badge; presentation-only, routing/fallback order is unaffected (#7775).
- feat(providers): let any provider connection — built-in or custom — be pinned to an explicit routing tier (free/cheap/premium) via a new `/api/settings/tier-config` route and an Advanced Settings tier selector; `TierCoverageWidget` now honors the override too (#7818)
- feat(routing): add a read-only `GET /v1/auto-combo/{channel}/candidates` endpoint listing an `auto/*` channel's candidate pool with live reachability (provider circuit breaker, connection cooldown, model lockout), plus a per-API-key candidate exclusion list enforced in the `auto/*` candidate-pool builder — users with no overrides configured see byte-identical routing to today (#7819).
- **feat(shared):** Add canonical numeric coercion helpers (`toNumber`, `toNumberOrNull`, `toNumberArray`) in `src/shared/utils/numeric.ts` with table-driven tests and a lint rule barring new local `toNumber` definitions, then migrate the report/analytics tier (usage analytics route, provider metrics route, usage stats, usage history helpers, call-log formatting, cost-analysis A2A skill) off their local duplicates ([#7879](https://github.com/diegosouzapw/OmniRoute/issues/7879))
- feat(mcp): add a narrow `mcp:connect` API-key scope for the `/api/mcp/` LOCAL_ONLY carve-out (separate from `manage`/`admin`) and populate `authInfo.scopes` from the caller's real per-key scopes over HTTP/SSE so `scopeEnforcement.ts` prefers per-key scopes over the `OMNIROUTE_MCP_SCOPES` env fallback when enforcement is enabled (#7895).
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.